49

为什么std::vectoroperator[]成员函数没有指定front为?backnoexcept

4

3 回答 3

73

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

被标记的函数是swap(不能失败,因为异常安全通常依赖于此)和numeric_limits::min(不能失败,返回原始类型的常量)。

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

于 2013-12-11T10:59:43.643 回答
16

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

noexcept 和 std::vector

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

使用复制构造函数扩展向量

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

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

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

noexcept 和移动

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

假设在移动过程中抛出异常,之前的主干与之前move发生的不一样:资源被盗,向量处于损坏状态。用户无法处理异常,因为一切都处于不确定状态。

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

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

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


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

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

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

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


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

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

于 2013-12-11T14:12:51.140 回答
5

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

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

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

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

于 2014-10-13T00:47:09.357 回答