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