C ++ method that may / may not return structure

I have a C ++ structure and method:

struct Account { unsigned int id; string username; ... }; Account GetAccountById(unsigned int id) const { } 

I can return the account structure if the account exists, but what if there is no account?

I thought about:

  • The flag "is valid" in the structure (therefore, it can be returned empty, with the false value set)
  • Optional pointer "is valid" (const string & id, int * is_ok), which sets if the output is valid
  • Returns the account * instead and returns either a pointer to a structure, or NULL if it does not exist?

Is there a better way to do this?

+8
c ++ struct
source share
11 answers

You forgot the most obvious in C ++:

 bool GetAccountById(unsigned int id, Account& account); 

Return true and fill in the provided link, if the account exists, else return false .

It may also be convenient to use the fact that pointers can be null and have:

 bool GetAccountById(unsigned int id, Account* account); 

This can be determined to return true if the account identifier exists, but only (of course) to populate the provided account if the pointer is not equal to zero. Sometimes it is convenient for testing for existence, and it saves the need to have a dedicated method for this purpose only.

It is a matter of taste that you prefer to have.

+13
source share

From the given options I will return Account* . But a return pointer can have a bad side effect for an interface.

Another possibility is to throw exception when there is no such account. You can also try boost::optional .

+7
source share

You can also try the null object pattern .

+6
source share

It depends on how likely it is that you consider a non-existent account.

If this is truly exceptional - deep in the bowels of the domestic banking system, where the data must be valid - then perhaps throw an exception.

If it is at the user interface level while checking the data, you may not have selected an exception.

Returning the pointer means that someone must free the allocated memory - this is messier.

Can you use a "token id" (for example, 0) to indicate an "invalid account"?

+3
source share

I would use Account* and add a comment to the method, indicating that the return value may be NULL.

+2
source share

There are several methods.

1) Throw an exception. This is useful if you want GetAccountById return an account by value, and using exceptions is appropriate for your programming model. Some will tell you that exceptions are "intended" for use only in exceptional circumstances. Things like “out of memory” or “computer on fire”. This is very controversial, and for each programmer you will find who says that exceptions are not for controlling the flow of data, you will find others (including myself), which says that exceptions can be used for controlling the flow. You need to think about it and decide for yourself.

 Account GetAccountById(unsigned int id) const { if( account_not_found ) throw std::runtime_error("account not found"); } 

2) Do not return and Account by value. Instead, return the pointer (preferably a smart pointer) and return NULL when you did not find the account:

 boost::shared_ptr<Account> GetAccountById(unsigned int id) const { if( account_not_found ) return NULL; } 

3) Return an object that has a presence flag indicating whether a data item is present. Boost.Optional is an example of such a device, but if you cannot use Boost here, it is a template object with bool member true when the data element is present, and false when it is absent. The data element itself is stored in the value_ element. It should be constructive by default.

 template<class Value> struct PresenceValue { PresenceValue() : present_(false) {}; PresenceValue(const Value& val) : present_(true), value_(val) {}; PresenceValue(const PresenceValue<Value>& that) : present_(that.present_), value_(that.value_) {}; explicit PresenceValue(Value val) : present_(true), value_(val) {}; template<class Conv> explicit PresenceValue(const Conv& conv) : present_(true), value_(static_cast<Value>(conv)) {}; PresenceValue<Value>& operator=(const PresenceValue<Value>& that) { present_ = that.present_; value_ = that.value_; return * this; } template<class Compare> bool operator==(Compare rhs) const { if( !present_ ) return false; return rhs == value_; } template<class Compare> bool operator==(const Compare* rhs) const { if( !present_ ) return false; return rhs == value_; } template<class Compare> bool operator!=(Compare rhs) const { return !operator==(rhs); } template<class Compare> bool operator!=(const Compare* rhs) const { return !operator==(rhs); } bool operator==(const Value& rhs) const { return present_ && value_ == rhs; } operator bool() const { return present_ && static_cast<bool>(value_); } operator Value () const; void Reset() { value_ = Value(); present_ = false; } bool present_; Value value_; }; 

For simplicity, I will create a typedef for Account :

 typedef PresenceValue<Account> p_account; 

... and then return this from your function:

 p_account GetAccountByIf(...) { if( account_found ) return p_account(the_account); // this will set 'present_' to true and 'value_' to the account else return p_account(); // this will set 'present_' to false } 

Using this is simple:

 p_account acct = FindAccountById(some_id); if( acct.present_ ) { // magic happens when you found the account } 
+2
source share

Another way, besides returning the link, is to return the pointer. If the account exists, return its pointer. Otherwise, return NULL.

0
source share

There is another way, similar to the "really" template. I am currently developing an application in which there are many such things. But my identifiers can never be less than 1 (all SERIAL fields in the PostgreSQL database), so I have a default constructor for each structure (or class in my case) that initializes the id using the -1 method and isValid() which returns true if id not -1. Works great for me.

0
source share

I would do:

 class Bank { public: class Account {}; class AccountRef { public: AccountRef(): m_account(NULL) {} AccountRef(Account const& acc) m_account(&acc) {} bool isValid() const { return m_account != NULL);} Account const& operator*() { return *m_account; } operator bool() { return isValid(); } private: Account const* m_account; }; Account const& GetAccountById(unsigned int id) const { if (id < m_accounts.size()) { return m_accounts[id]; } throw std::outofrangeexception("Invalid account ID"); } AccountRef FindAccountById(unsigned int id) const { if (id < m_accounts.size()) { return AccountRef(m_accounts[id]); } return AccountRef(); } private: std::vector<Account> m_accounts; }; 

A method called get should always return (IMHO) the requested object. If it does not exist, then this is an exception. If it is likely that something might not exist, you should also provide a find method that can determine if the object exists so that the user can test it.

 int main() { Bank Chase; // Get a reference // As the bank ultimately ownes the account. // You just want to manipulate it. Account const& account = Chase.getAccountById(1234); // If there is the possibility the account does not exist then use find() AccountRef ref = Chase.FindAccountById(12345); if ( !ref ) { // Report error return 1; } Account const& anotherAccount = *ref; } 

Now I could use a pointer instead of making an effort to create an AccountRef. The problem is that pointers do not have ownership sympathies and therefore there is no true indication of who should (and therefore remove) the pointer.

As a result, I like to transfer pointers to some container that allows the user to manipulate the object only the way I want them too. In this case, the AccountRef does not set a pointer, so the AccountRef user has no way to actually try and delete the account.

Here you can check whether the AccountRef is valid and retrieve the link to the account (provided that it is valid). Since the object contains only a pointer, the compiler can optimize it to such an extent that it is no more expensive than passing a pointer. The advantage is that the user cannot accidentally abuse what I gave them.

Summary: AccountRef has no real value at runtime. However, it provides type safety (since it hides the use of a pointer).

0
source share

I like to do a combination of what you offer with the Valid flag and what someone else suggested with a null object template.

I have a base class called Status , which I inherit from the objects that I want to use as return values. I will leave most of this discussion because it is a bit larger, but it looks something like this.

 class Status { public: Status(bool isOK=true) : mIsOK(isOK) operator bool() {return mIsOK;} private bool mIsOK }; 

now you will have

 class Account : public Status { public: Account() : Status(false) Account(/*other parameters to initialize an account*/) : ... ... }; 

Now, if you create an account without parameters:

 Account A; 

Not valid. But if you create an account with data

 Account A(id, name, ...); 

It's really.

You validate using the bool operator.

 Account A=GetAccountByID(id); if (!A) { //whoa there! that an invalid account! } 

I do this a lot when I work with math types. For example, I do not want to write a function that looks like this:

 bool Matrix_Multiply(a,b,c); 

where a, b, and c are matrices. I would rather write

 c=a*b; 

with operator overload. But there are times when a and b cannot be multiplied, so they are not always valid. So they just return invalid c if it doesn't work and I can do

 c=a*b; if (!c) //handle the problem. 
0
source share

boost :: optional is probably the best thing you can do in a language, so it’s broken, it doesn’t have its own options.

-one
source share

Source: https://habr.com/ru/post/650092/


All Articles