我有一个C ++结构和一个方法:
struct Account
{
unsigned int id;
string username;
...
};
Account GetAccountById(unsigned int id) const { }
如果帐户存在,我可以返回帐户结构,但如果没有帐户该怎么办?
我想过:
有最好的方法吗?
答案 0 :(得分:13)
你忘记了最明显的一个,用C ++:
bool GetAccountById(unsigned int id, Account& account);
如果帐户存在,请返回true
并填写提供的参考,否则返回false
。
使用指针可以为null并且具有:
的事实也可能是方便的bool GetAccountById(unsigned int id, Account* account);
如果帐户ID存在,则可以将其定义为返回true
,但如果指针非空,则仅(当然)填写提供的帐户。有时候能够测试存在是否方便,这样就可以节省必须专门用于此目的的方法。
这是你喜欢的品味问题。
答案 1 :(得分:7)
根据给出的选项,我将返回Account*
。但返回指针可能会对界面产生一些不良影响。
另一种可能性是在没有此类帐户时throw
例外。您也可以尝试boost::optional
。
答案 2 :(得分:6)
您也可以尝试null object pattern。
答案 3 :(得分:3)
这取决于您认为不存在的帐户的可能性。
如果它确实非常特殊 - 深入银行系统内部的数据应该是有效的 - 那么可能会抛出异常。
如果它在用户界面级别,验证数据,则可能不会抛出异常。
返回指针意味着有人必须释放已分配的内存 - 这更加混乱。
您可以使用“标记ID”(例如0)来表示“帐户无效”吗?
答案 4 :(得分:2)
我会使用Account*
并向方法添加文档注释,声明返回值可以为NULL。
答案 5 :(得分:2)
有几种方法。
1)抛出异常。如果您希望GetAccountById
按值返回帐户,并且异常的使用符合您的编程模型,则此功能非常有用。有些人会告诉你,只有在特殊情况下才会使用例外情况。像“内存不足”或“计算机着火”之类的东西。这是非常值得商榷的,对于每个程序员而言,如果您发现异常不是流量控制的话,您会发现另一个(包括我自己)说异常可以用于流量控制。你需要考虑这个并自己决定。
Account GetAccountById(unsigned int id) const
{
if( account_not_found )
throw std::runtime_error("account not found");
}
2)不要按值返回Account
。相反,返回指针(最好是智能指针),并在找不到帐户时返回NULL:
boost::shared_ptr<Account> GetAccountById(unsigned int id) const
{
if( account_not_found )
return NULL;
}
3)返回一个具有'presence'标志的对象,该标志指示数据项是否存在。 Boost.Optional是此类设备的示例,但是如果您不能在此处使用Boost,则会在数据项存在时模板化对象,其bool
成员为true
,如果不是,则为false
。数据项本身存储在value_
成员中。它必须是默认的可构造的。
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_;
};
为简单起见,我会为Account
创建一个typedef:
typedef PresenceValue<Account> p_account;
...然后从你的函数中返回:
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
}
使用它很简单:
p_account acct = FindAccountById(some_id);
if( acct.present_ )
{
// magic happens when you found the account
}
答案 6 :(得分:0)
除了返回引用之外的另一种方法是返回指针。如果帐户存在,则返回其指针。否则,返回NULL。
答案 7 :(得分:0)
还有另一种类似于“有效”模式的方式。我正在开发一个现在有很多这样的东西的应用程序。但我的ID永远不会小于1(它们都是PostgreSQL数据库中的SERIAL字段)所以我只为每个结构(或我的情况下的类)都有一个默认构造函数,它用-1和{{初始化id
1}}如果isValid()
不等于-1,则返回true的方法。适合我。
答案 8 :(得分:0)
我愿意:
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;
};
一个名为get的方法应该总是返回(恕我直言)所要求的对象。如果它不存在那么这是一个例外。如果某些内容可能不存在,那么您还应该提供一个find方法,以确定该对象是否存在,以便用户可以对其进行测试。
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;
}
现在我可以使用指针而不是创建AccountRef。问题在于指针没有所有权语义,因此没有真正指示谁应该拥有(并因此删除)指针。
因此,我喜欢将指针包装在某个容器中,允许用户仅按照我的需要操作对象。在这种情况下,AccountRef不会公开指针,因此AccountRef用户无法实际尝试删除该帐户。
在这里,您可以检查AccountRef是否有效并提取对帐户的引用(假设它是有效的)。因为对象只包含一个指针,所以编译器可能会对此进行优化,以至于这并不比传递指针更昂贵。好处是用户不会不小心滥用我给他们的东西。
总结:AccountRef没有实际的运行时成本。然而提供了类型安全性(因为它隐藏了指针的使用)。
答案 9 :(得分:0)
我喜欢将您建议的内容与Valid标记以及其他人建议的null对象模式结合使用。
我有一个名为Status
的基类,我继承自我想要用作返回值的对象。我会把大部分内容都放在讨论之外,因为它涉及的内容有点多,但它看起来像这样
class Status
{
public:
Status(bool isOK=true) : mIsOK(isOK)
operator bool() {return mIsOK;}
private
bool mIsOK
};
现在你已经
了class Account : public Status
{
public:
Account() : Status(false)
Account(/*other parameters to initialize an account*/) : ...
...
};
现在,如果您创建一个没有参数的帐户:
Account A;
它无效。但是,如果您创建一个包含数据的帐户
Account A(id, name, ...);
这是有效的。
您可以使用操作员bool测试有效性。
Account A=GetAccountByID(id);
if (!A)
{
//whoa there! that's an invalid account!
}
当我使用数学类型时,我做了很多。例如,我不想编写看起来像这样的函数
bool Matrix_Multiply(a,b,c);
其中a,b和c是矩阵。我更愿意写
c=a*b;
运算符重载。但是有些情况下a和b不能相乘,所以它并不总是有效的。所以他们只是返回一个无效的c,如果它不起作用,我可以做
c=a*b;
if (!c) //handle the problem.
答案 10 :(得分:-1)
boost :: optional可能是你能用语言做的最好的,所以它没有本机变种。