我知道大多数人认为这是一种不好的做法但是当你试图使你的类公共接口只使用引用,保持指针内部并且只在必要时,我认为没有办法回报一些东西告诉你正在寻找的价值容器中不存在.
class list { public: value &get(type key); };
让我们认为你不希望在类的公共接口中看到危险的指针,在这种情况下如何返回未找到的,抛出异常?
你对此有何看法?你是否返回一个空值并检查它的空状态?我实际上使用throw方法,但我介绍了一种检查方法:
class list { public: bool exists(type key); value &get(type key); };
因此,当我忘记检查值是否存在时,我得到一个异常,这实际上是一个例外.
你会怎么做?
STL通过使用迭代器来处理这种情况.例如,std :: map类具有类似的功能:
iterator find( const key_type& key );
如果找不到密钥,则返回'end()'.您可能希望使用此迭代器方法,或者使用某种包装器作为返回值.
正确的答案(根据Alexandrescu说)是:
Optional
和强制
首先,使用Accessor,但是在没有发明轮子的情况下以更安全的方式使用:
boost::optionalget_X_if_possible();
然后创建一个enforce
帮助器:
templateT& enforce(boost::optional & opt, E e = std::runtime_error("enforce failed")) { if(!opt) { throw e; } return *opt; } // and an overload for T const &
这样,根据缺少值的含义,您可以明确检查:
if(boost::optionalmaybe_x = get_X_if_possible()) { X& x = *maybe_x; // use x } else { oops("Hey, we got no x again!"); }
或隐含地:
X& x = enforce(get_X_if_possible()); // use x
当您担心效率时,或者当您想要在发生故障时处理故障时,您可以使用第一种方式.第二种方式适用于所有其他情况.
在这种情况下不要使用例外.对于此类异常,C++具有非常重要的性能开销,即使没有抛出异常,它也会使代码的推理更加困难(参见异常安全性).
C++中的最佳实践是以下两种方式之一.两者都在STL中使用:
正如Martin指出的那样,返回一个迭代器.实际上,你的迭代器很可能是typedef
一个简单的指针,没有任何反对它的东西; 事实上,由于这与STL一致,你甚至可以说这种方式优于返回引用.
回来一个std::pair
.但这使得无法修改该值,因为pair
调用的copycon 不能与referende成员一起使用.
这个答案引起了一些争议,从评论中可以看出,并且从它得到的许多贬值中看不到.我发现这很令人惊讶.
这个答案从未被视为最终的参考点.马丁已经给出了"正确"的答案:例外反映了这种情况下的行为相当糟糕.使用除异常之外的其他信令机制在语义上更有意义.
精细.我完全赞同这种观点.无需再次提及它.相反,我想为答案提供额外的方面.虽然轻微的速度提升永远不应该是任何决策的第一个理由,但它们可以提供进一步的论据,而在一些(少数)情况下,它们甚至可能是至关重要的.
实际上,我已经提到了两个方面:性能和异常安全.我相信后者是相当无可争议的.虽然提供强大的异常保证非常困难(当然,最强大的是"nothrow"),但我认为这是必不可少的:任何保证不会抛出异常的代码都会使整个程序更容易推理.许多C++专家都强调这一点(例如Scott Meyers在"Effective C++"第29项中).
关于速度.Martin York指出,这不再适用于现代编译器.我恭敬地不同意.C++语言使得环境必须在运行时跟踪在异常情况下可能解开的代码路径.现在,这个开销实际上并不是那么大(并且很容易验证这一点).我上面的文字中"非常重要"可能过于强烈.
但是,我发现在C++这样的语言和许多现代的"托管"语言(如C#)之间进行区分非常重要.只要没有抛出异常,后者就没有额外的开销,因为无论如何都要保持展开堆栈所需的信息.总的来说,坚持我的选择.
exists()的问题是你最终会搜索两次存在的东西(首先检查它是否在那里,然后再找到它).这是低效的,特别是如果(因为它的名称"列表"暗示)你的容器是搜索是O(n)的容器.
当然,你可以做一些内部缓存以避免双重搜索,但是你的实现变得更加混乱,你的类变得不那么通用了(因为你已针对特定情况进行了优化),并且它可能不会是异常安全或线程-安全.