为什么矢量访问运算符未指定为noexcept?

时间:2013-12-11 10:56:10

标签: c++ exception c++11 stl noexcept

为什么std::vector的{​​{1}},operator[]front成员函数未指定为back

3 个答案:

答案 0 :(得分:63)

noexcept上的标准政策是仅标记不能不能失败的函数,而不是那些仅指定不抛出异常的函数。换句话说,所有具有有限域的函数(传递错误的参数并且您得到未定义的行为)都不是noexcept,即使它们未被指定要抛出。

标记的函数是swap之类的东西(绝不能失败,因为异常安全通常依赖于此)和numeric_limits::min(不能失败,返回基本类型的常量)。

原因是实现者可能希望提供其库的特殊调试版本,这些版本会引发各种未定义的行为情况,以便测试框架可以轻松检测到错误。例如,如果您使用带有vector::operator[]的越界索引,或者在空向量上调用frontback。有些实现想要在那里抛出一个异常(它们被允许:因为它是未定义的行为,它们可以做任何事情),但是这些函数上的标准强制noexcept使得这不可能。

答案 1 :(得分:15)

作为@SebastianRedl回答的补充:为什么你需要noexcept

noexcept和std :: vector

您可能已经知道,vector有其容量。如果它在push_back时已满,它将分配更大的内存,将所有现有元素复制(或从C ++ 11移动)到新主干,然后将新元素添加到后面。

Use copy constructor to expand a vector

但是如果在分配内存或将元素复制到新主干时抛出异常怎么办?

  • 如果在分配内存期间抛出异常,则向量处于其原始状态。只需重新抛出异常并让用户处理它就可以了。

  • 如果在复制现有元素期间抛出异常,则通过调用析构函数将销毁所有复制的元素,释放已分配的中继,并抛出异常以由用户代码处理。 (1)
    在摧毁一切之后,矢量返回到原始状态。现在可以安全地抛出异常让用户处理它,而不会泄漏任何资源。

noexcept并移动

来到C ++ 11时代,我们有一个名为move的强大武器。它允许我们从未使用的对象中窃取资源。 std :: vector将在需要增加(或减少)容量时使用move ,只要 move操作 noexcept

假设在移动过程中抛出异常,前一个中继与move发生前的中断不同:资源被盗,使向量处于损坏的状态。用户无法处理异常,因为所有内容都处于非确定状态。

Use move constructor to expand a vector

这就是std::vector依赖move constructor noexcept 的原因。

这是客户端代码如何依赖noexcept作为接口规范的演示。如果以后不满足noexcept要求,以前依赖它的任何代码都将被破坏。


为什么不简单地将所有功能标记为noexcept

简短回答:异常安全代码很难写。

长答案:noexcept为实施界面的开发人员设置了严格的限制。如果要从接口中删除noexcept,客户端代码可能会像上面给出的向量示例一样被破坏;但是如果你想创建一个界面noexcept,你可以随时自由地进行。{/ p>

因此,仅在必要时,将界面标记为noexcept


Going Native 2013中,Scott Meyers谈到了上述情况,即如果没有noexcept,程序的完整性就会失败。

我还写了一篇关于它的博客:https://xinhuang.github.io/posts/2013-12-31-when-to-use-noexcept-and-when-to-not.html

答案 2 :(得分:4)

简而言之,有或没有noexcept指定的功能。这是有意的,因为它们是不同的。原则是:指定未定义行为的函数(例如,由于参数不正确)不应与noexcept一起使用。

This paper明确指定这些成员没有noexceptvector的一些成员被用作例子:

  

具有广泛合同的函数示例为vector<T>::begin()vector<T>::at(size_type)。没有广泛合同的函数示例包括vector<T>::front()vector<T>::operator[](size_type)

有关初步动机和详细讨论,请参阅this paper。这里最明显的现实问题是可测试性。