为什么 std::vector
的 operator[]
、front
和 back
成员函数没有被指定为 noexcept
?
noexcept
标记,而不是那些简单地规定不会抛出异常的函数。换句话说,所有具有有限域的函数(传递错误的参数会导致未定义行为)都不是noexcept
,即使它们没有指定抛出异常。swap
(不能失败,因为异常安全通常依赖于它)和numeric_limits :: min
(不能失败,返回原始类型的常量)这样的东西。vector :: operator []
中使用越界索引,或在空向量上调用front
或back
。一些实现可能想要在那里抛出异常(他们可以:由于它是未定义行为,他们可以做任何事情),但标准规定的noexcept
对这些函数的使用将变得不可能。noexcept
?
< h2 > noexcept和std::vector < /h2 >
正如您所知,vector
有其容量。如果在push_back
时已满,它将分配更大的内存,将所有现有元素复制(自C++11以来移动)到新的主干,然后将新元素添加到末尾。
但是如果在分配内存或将元素复制到新的容器时抛出异常怎么办?
如果在分配内存期间抛出异常,则向量仍处于原始状态。只需重新抛出异常并让用户处理即可。
如果在复制现有元素期间抛出异常,则通过调用析构函数销毁所有已复制的元素,释放分配的容器,并将异常抛出以由用户代码处理。(1)
在销毁所有内容后,向量回到原始状态。现在可以安全地抛出异常以让用户处理,而不会泄漏任何资源。
进入C++ 11时代,我们拥有了一个强大的工具——move
。它允许我们从未使用的对象中窃取资源。当std::vector需要增加(或减少)容量时,将使用move
,只要move
操作是noexcept的。
move
之前不同:资源被窃取,导致向量处于 破碎 状态。用户无法处理异常,因为一切都处于不确定状态。
std::vector
依赖于移动构造函数
来保证不抛出异常。noexcept
作为接口规范。如果后来不满足noexcept
要求,之前依赖它的任何代码都将被破坏。
noexcept
?简短回答:编写异常安全代码很困难。
详细回答:使用 noexcept
会对实现接口的开发人员设置严格限制。如果您想从接口中删除 noexcept
,则客户端代码可能会出现问题,就像上面给出的 vector 示例一样;但是如果您想将接口标记为 noexcept
,则可以随时自由地这样做。
因此,只有在必要时才将接口标记为 noexcept
。
noexcept
的情况下,程序的健壮性将会失败。
noexcept
的函数实际上可能会抛出异常。然而,考虑到代码生成的实际情况以及这种调试模式将未定义行为转换为已定义行为(抛出异常),使得这种方法不可行于编写C++实现。如果一个函数被标记为noexcept
,那么调用者可能会缺少撤销表,因此尝试解开异常将导致崩溃或使执行进入无尽的状态,这就有点达不到抛出异常的目的了。 - Sebastian Redlnoexcept
并且接受一个T
,那么该函数声明它不能/不应该在任何T
值上失败。基于给出的示例,vector::operator[](i) noexcept
可能会像vector::at()
一样执行边界检查,并在任何i >= size()
的情况下返回一个哨兵值,而不是抛出异常。 - Justin Time - Reinstate Monica