C++ method that can/cannot return a struct
I have a C++ stru开发者_运维问答ct and a method:
struct Account
{
unsigned int id;
string username;
...
};
Account GetAccountById(unsigned int id) const { }
I can return an Account struct if the account exists, but what to do if there's no account?
I thought of having:
- An "is valid" flag on the struct (so an empty one can be returned, with that set to false)
- An additional "is valid" pointer (const string &id, int *is_ok) that's set if the output is valid
- Returning an Account* instead, and returning either a pointer to a struct, or NULL if it doesn't exist?
Is there a best way of doing this?
You forgot the most obvious one, in C++:
bool GetAccountById(unsigned int id, Account& account);
Return true
and fill in the provided reference if the account exists, else return false
.
It might also be convenient to use the fact that pointers can be null, and having:
bool GetAccountById(unsigned int id, Account* account);
That could be defined to return true
if the account id exists, but only (of course) to fill in the provided account if the pointer is non-null. Sometimes it's handy to be able to test for existance, and this saves having to have a dedicated method for only that purpose.
It's a matter of taste what you prefer having.
From the options given I would return Account*
. But returning pointer may have some bad side effect on the interface.
Another possibility is to throw
an exception when there is no such account. You may also try boost::optional
.
You could also try the null object pattern.
It depends how likely you think the non-existent account is going to be.
If it is truly exceptional - deep in the bowels of the internals of the banking system where the data is supposed to be valid - then maybe throw an exception.
If it is in a user-interface level, validating the data, then probably you don't throw an exception.
Returning a pointer means someone has to deallocate the allocated memory - that's messier.
Can you use an 'marker ID' (such as 0) to indicate 'invalid account'?
I would use Account*
and add a documentation comment to the method stating that the return value can be NULL.
There are several methods.
1) Throw an exception. This is useful if you want GetAccountById
to return the account by value and the use of exceptions fit your programming model. Some will tell you that exceptions are "meant" to be used only in exceptional circumstances. Things like "out of memory" or "computer on fire." This is highly debatable, and for every programmer you find who says exceptions are not for flow control you'll find another (myself included) who says that exceptions can be used for flow control. You need to think about this and decide for yourself.
Account GetAccountById(unsigned int id) const
{
if( account_not_found )
throw std::runtime_error("account not found");
}
2) Don't return and Account
by value. Instead, return by pointer (preferably smart pointer), and return NULL when you didn't 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 or not the data item is present. Boost.Optional is an example of such a device, but in case you can't use Boost here is a templated object that has a bool
member that is true
when the data item is present, and is false
when it is not. The data item itself is stored in the value_
member. It must be default constructible.
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 would 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 straightforward:
p_account acct = FindAccountById(some_id);
if( acct.present_ )
{
// magic happens when you found the account
}
Another way besides returning a reference is to return a pointer. If the account exists, return its pointer. Else, return NULL.
There is yet another way similar to the "is valid" pattern. I am developing an application right now that has a lot of such stuff in it. But my IDs can never be less than 1 (they are all SERIAL fields in a PostgreSQL database) so I just have a default constructor for each structure (or class in my case) that initializes id
with -1 and isValid()
method that returns true if id
is not equal to -1. Works perfectly for me.
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 object asked for. If it does not exist then that is an exception. If there is the possibility that something may not exists then you should also provide a find method that can determine if the object exists so that a 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 have used a pointer instead of going to the effort of creating AccountRef. The problem with that is that pointers do not have ownership symantics and thus there is no true indication of who should own (and therefore delete) the pointer.
As a result I like to wrap pointers in some container that allows the user to manipulate the object only as I want them too. In this case the AccountRef does not expose the pointer so there is no opportunity for the user of AccountRef to actually try and delete the account.
Here you can check if AccountRef is valid and extract a reference to an account (assuming it is valid). Because the object contains only a pointer the compiler is liable to optimize this to the point that this is no more expensive than passing the pointer around. The benefit is that the user can not accidentally abuse what I have given them.
Summary: AccountRef has no real run-time cost. Yet provides type safety (as it hides the use of pointer).
I like to do a combination of what you suggest with the Valid flag and what someone else suggested with the null object pattern.
I have a base class called Status
that I inherit from on objects that I want to use as return values. I'll leave most of it out of this discussion since it's a little more involved but it looks something like this
class Status
{
public:
Status(bool isOK=true) : mIsOK(isOK)
operator bool() {return mIsOK;}
private
bool mIsOK
};
now you'd have
class Account : public Status
{
public:
Account() : Status(false)
Account(/*other parameters to initialize an account*/) : ...
...
};
Now if you create an account with no parameters:
Account A;
It's invalid. But if you create an account with data
Account A(id, name, ...);
It's valid.
You test for the validity with the operator bool.
Account A=GetAccountByID(id);
if (!A)
{
//whoa there! that's an invalid account!
}
I do this a lot when I'm working with math-types. For example, I don't want to have to write a function that looks like this
bool Matrix_Multiply(a,b,c);
where a, b, and c are matrices. I'd much rather write
c=a*b;
with operator overloading. But there are cases where a and b can't be multiplied so it's not always valid. So they just return an invalid c if it doesn't work, and I can do
c=a*b;
if (!c) //handle the problem.
boost::optional is probably the best you can do in a language so broken it doesn't have native variants.
精彩评论