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

liz*_*isk 43 c++ stl exception noexcept c++11

为什么std::vectoroperator[],frontback成员函数没有被指定为noexcept

Seb*_*edl 66

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

被标记的函数是swap(必须不会失败,因为异常安全通常依赖于它)和numeric_limits::min(不能失败,返回基本类型的常量).

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

  • @PlasmaHH理论上,你是对的.一旦调用了未定义的行为,"noexcept"函数就可以实际展开.但是,代码生成的实际考虑因素以及此类调试模式将未定义的行为转换为定义的行为(抛出异常)这一事实使得这成为编写C++实现的不可行方法.如果一个函数是`noexcept`,那么调用者很可能会丢失展开表,因此尝试展开会崩溃或将执行发送到永不落伍的地方,这种情况会导致抛出异常. (7认同)
  • 超出范围是未定义的行为,并且在未定义的行为的情况下,任何事情都可能发生.未定义的行为不受noexcept的限制.虽然发生异常时,noexcept肯定会终止,这在测试框架中可能是不可取的. (2认同)

Xin*_*ang 16

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

noexcept和std :: vector

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

使用复制构造函数来展开向量

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

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

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

noexcept和move

来到C++ 11时代,我们有一个强大的武器叫做move.它允许我们从未使用的对象中窃取资源.的std ::矢量将使用move时,它需要增加(或减少)的能力,只要move操作是noexcept.

假设在移动期间抛出异常,前一个中继与之前move中断不同:资源被盗,使向量处于中断状态.用户无法处理异常,因为所有内容都处于非确定状态.

使用移动构造函数展开向量

这就是为什么std::vector依赖于move constructornoexcept.

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


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

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

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

因此,仅在必要时将接口标记为noexcept.


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

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


Fra*_*kHB 5

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

本文明确指出这些成员没有noexcept。的一些成员vector被用作示例:

具有广泛契约的函数的例子是vector<T>::begin()vector<T>::at(size_type)。没有广泛契约的函数的例子是vector<T>::front()vector<T>::operator[](size_type)

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