为什么std::vector
的{{1}},operator[]
和front
成员函数未指定为back
?
答案 0 :(得分:63)
noexcept
上的标准政策是仅标记不能或不能失败的函数,而不是那些仅指定不抛出异常的函数。换句话说,所有具有有限域的函数(传递错误的参数并且您得到未定义的行为)都不是noexcept
,即使它们未被指定要抛出。
标记的函数是swap
之类的东西(绝不能失败,因为异常安全通常依赖于此)和numeric_limits::min
(不能失败,返回基本类型的常量)。
原因是实现者可能希望提供其库的特殊调试版本,这些版本会引发各种未定义的行为情况,以便测试框架可以轻松检测到错误。例如,如果您使用带有vector::operator[]
的越界索引,或者在空向量上调用front
或back
。有些实现想要在那里抛出一个异常(它们被允许:因为它是未定义的行为,它们可以做任何事情),但是这些函数上的标准强制noexcept
使得这不可能。
答案 1 :(得分:15)
作为@SebastianRedl回答的补充:为什么你需要noexcept
?
您可能已经知道,vector
有其容量。如果它在push_back
时已满,它将分配更大的内存,将所有现有元素复制(或从C ++ 11移动)到新主干,然后将新元素添加到后面。
但是如果在分配内存或将元素复制到新主干时抛出异常怎么办?
如果在分配内存期间抛出异常,则向量处于其原始状态。只需重新抛出异常并让用户处理它就可以了。
如果在复制现有元素期间抛出异常,则通过调用析构函数将销毁所有复制的元素,释放已分配的中继,并抛出异常以由用户代码处理。 (1)
在摧毁一切之后,矢量返回到原始状态。现在可以安全地抛出异常让用户处理它,而不会泄漏任何资源。
来到C ++ 11时代,我们有一个名为move
的强大武器。它允许我们从未使用的对象中窃取资源。 std :: vector将在需要增加(或减少)容量时使用move
,只要 move
操作 noexcept 。
假设在移动过程中抛出异常,前一个中继与move
发生前的中断不同:资源被盗,使向量处于损坏的状态。用户无法处理异常,因为所有内容都处于非确定状态。
这就是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明确指定这些成员没有noexcept
。 vector
的一些成员被用作例子:
具有广泛合同的函数示例为
vector<T>::begin()
和vector<T>::at(size_type)
。没有广泛合同的函数示例包括vector<T>::front()
和vector<T>::operator[](size_type)
。
有关初步动机和详细讨论,请参阅this paper。这里最明显的现实问题是可测试性。