为什么不将向量访问操作符指定为noexcept?

时间:2021-06-30 22:01:47

Why std::vector's operator[], front and back member functions are not specified as noexcept?

为什么std::向量算子[],前、后成员函数不指定为noexcept?

3 个解决方案

#1


57  

The standard's policy on noexcept is to only mark functions that cannot or must not fail, but not those that simply are specified not to throw exceptions. In other words, all functions that have a limited domain (pass the wrong arguments and you get undefined behavior) are not noexcept, even when they are not specified to throw.

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

Functions that get marked are things like swap (must not fail, because exception safety often relies on that) and numeric_limits::min (cannot fail, returns a constant of a primitive type).

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

The reason is that implementors might want to provide special debug versions of their libraries that throw on various undefined behavior situations, so that test frameworks can easily detect the error. For example, if you use an out-of-bound index with vector::operator[], or call front or back on an empty vector. Some implementations want to throw an exception there (which they are allowed to: since it's undefined behavior, they can do anything), but a standard-mandated noexcept on those functions makes this impossible.

原因是实现者可能想要为他们的库提供特殊的调试版本,这些版本包含各种未定义的行为情况,以便测试框架能够很容易地检测到错误。例如,如果您使用一个带vector:::operator[]的超界索引,或者在一个空的向量上调用front或back。一些实现想要在那里抛出一个异常(他们可以这样做:因为它是未定义的行为,所以他们可以做任何事情),但是这些函数上的一个标准强制的noexcept使得这成为不可能的。

#2


13  

As a complimentary to @SebastianRedl 's answer: why you will need noexcept?

作为对@SebastianRedl的回答的补充:为什么你不需要除了?

noexcept and std::vector

As you might have known, a vector has its capacity. If it's full when push_back, it will allocate a bigger memory, copy(or move if C++ 11) all existing elements to the new trunk, then add the new element to the back.

你可能知道,向量有它的容量。如果push_back时它是满的,那么它将分配更大的内存,将所有现有的元素复制(或者移动,如果是c++ 11)到新的主干中,然后将新的元素添加到后面。

为什么不将向量访问操作符指定为noexcept?

But what if an exception is thrown out while allocating memory, or copying the element to the new trunk?

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

  • If exception is thrown during allocating memory, the vector is in its original state. It's fine just re-throw the exception and let user handle it.

    如果在分配内存时抛出异常,则该向量处于原始状态。只要重新抛出异常并让用户处理就可以了。

  • If exception is thrown during copy existing elements, all copied elements will be destroyed by calling destructor, allocated trunk will be freed, and exception thrown out to be handle by user code. (1)
    After destroy everything, the vector is back to the original state. Now it's safe to throw exception to let user handle it, without leaking any resource.

    如果在复制现有元素时抛出异常,则通过调用析构函数销毁所有复制的元素,释放分配的主干,并抛出异常,由用户代码处理。(1)销毁所有东西后,矢量回到原来的状态。现在可以安全地抛出异常,让用户处理它,而不泄漏任何资源。

noexcept and move

Come to the era of C++ 11, we have a powerful weapon called move. It allows us to steal resources from unused objects. std::vector will use move when it needs to increase(or decrease) the capacity, as long as the move operation is noexcept.

在c++ 11时代,我们有一种强大的武器叫move。它允许我们从未使用的对象中窃取资源。std:::只要移动操作为noexcept,当需要增加(或减少)容量时,矢量使用move。

Suppose an exception throws during the move, the previous trunk is not the same as before move happens: resources are stolen, leaving the vector in an broken state. User cannot handle the exception because everything is in an nondeterministic state.

假设在移动过程中抛出一个异常,那么前一个主干与发生移动之前的主干并不相同:资源被窃取,使矢量处于破碎状态。用户无法处理异常,因为一切都处于不确定性状态。

为什么不将向量访问操作符指定为noexcept?

That's why std::vector relies on move constructor to be noexcept.

这就是std::vector依赖于move构造函数的原因。

This is a demostration how client code would rely on noexcept as an interface specification. If later the noexcept requirement is not met, any code previously depends on it will be broken.

这是一个降级客户端代码如何依赖noexcept作为接口规范。如果以后没有满足noexcept要求,以前依赖它的任何代码都将被破坏。


Why not simply mark all function as noexcept?

Short answer: exception safe code is hard to write.

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

Long answer: noexcept set a strict limit to developer who implement the interface. If you want to remove the noexcept from an interface, the client code might be broken like the vector example given above; but if you want to make an interface noexcept, you are free to do it at any time.

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

Thus, only when necessary, mark an interface as noexcept.

因此,只有在必要时,才将接口标记为noexcept。


In the Going Native 2013, Scott Meyers talked about above situation that without noexcept, the sanity of a program will fail.

在《Going Native 2013》中,斯科特•迈耶斯(Scott Meyers)谈到了上述情况:没有别的,程序的健全就会失败。

I also wrote a blog about it: https://xinhuang.github.io/posts/2013-12-31-when-to-use-noexcept-and-when-to-not.html

我也为此写了一篇博客:https://xinhuang.github.io/posts/2013-3-3-3-3-3-3-31 -when- use-no exception -when- no .html

#3


4  

In short, there are functions specified with or without noexcept. It is intended, because they are different. The principle is: a function with undefined behavior specified (e.g. due to improper arguments) should not be with noexcept.

简而言之,有带有或不带有noexcept的函数。它是有意的,因为它们是不同的。它的原理是:一个具有未定义行为的函数(例如,由于不适当的参数)不应该带有noexcept。

This paper explicitly specified these members being without noexcept. Some members of vector were used as examples:

本文明确规定了这些成员是无例外的。以向量的一些成员为例:

Examples of functions having wide contracts would be vector<T>::begin() and vector<T>::at(size_type). Examples of functions not having a wide contract would be vector<T>::front() and vector<T>::operator[](size_type).

函数具有广泛收缩的例子有向量 ::begin()和向量 ::at(size_type)。函数没有广泛收缩的例子有向量 ::front()和向量 :::operator[](size_type)。

See this paper for initial motivation and detailed discussion. The most obvious realistic problem here is testability.

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

#1


57  

The standard's policy on noexcept is to only mark functions that cannot or must not fail, but not those that simply are specified not to throw exceptions. In other words, all functions that have a limited domain (pass the wrong arguments and you get undefined behavior) are not noexcept, even when they are not specified to throw.

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

Functions that get marked are things like swap (must not fail, because exception safety often relies on that) and numeric_limits::min (cannot fail, returns a constant of a primitive type).

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

The reason is that implementors might want to provide special debug versions of their libraries that throw on various undefined behavior situations, so that test frameworks can easily detect the error. For example, if you use an out-of-bound index with vector::operator[], or call front or back on an empty vector. Some implementations want to throw an exception there (which they are allowed to: since it's undefined behavior, they can do anything), but a standard-mandated noexcept on those functions makes this impossible.

原因是实现者可能想要为他们的库提供特殊的调试版本,这些版本包含各种未定义的行为情况,以便测试框架能够很容易地检测到错误。例如,如果您使用一个带vector:::operator[]的超界索引,或者在一个空的向量上调用front或back。一些实现想要在那里抛出一个异常(他们可以这样做:因为它是未定义的行为,所以他们可以做任何事情),但是这些函数上的一个标准强制的noexcept使得这成为不可能的。

#2


13  

As a complimentary to @SebastianRedl 's answer: why you will need noexcept?

作为对@SebastianRedl的回答的补充:为什么你不需要除了?

noexcept and std::vector

As you might have known, a vector has its capacity. If it's full when push_back, it will allocate a bigger memory, copy(or move if C++ 11) all existing elements to the new trunk, then add the new element to the back.

你可能知道,向量有它的容量。如果push_back时它是满的,那么它将分配更大的内存,将所有现有的元素复制(或者移动,如果是c++ 11)到新的主干中,然后将新的元素添加到后面。

为什么不将向量访问操作符指定为noexcept?

But what if an exception is thrown out while allocating memory, or copying the element to the new trunk?

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

  • If exception is thrown during allocating memory, the vector is in its original state. It's fine just re-throw the exception and let user handle it.

    如果在分配内存时抛出异常,则该向量处于原始状态。只要重新抛出异常并让用户处理就可以了。

  • If exception is thrown during copy existing elements, all copied elements will be destroyed by calling destructor, allocated trunk will be freed, and exception thrown out to be handle by user code. (1)
    After destroy everything, the vector is back to the original state. Now it's safe to throw exception to let user handle it, without leaking any resource.

    如果在复制现有元素时抛出异常,则通过调用析构函数销毁所有复制的元素,释放分配的主干,并抛出异常,由用户代码处理。(1)销毁所有东西后,矢量回到原来的状态。现在可以安全地抛出异常,让用户处理它,而不泄漏任何资源。

noexcept and move

Come to the era of C++ 11, we have a powerful weapon called move. It allows us to steal resources from unused objects. std::vector will use move when it needs to increase(or decrease) the capacity, as long as the move operation is noexcept.

在c++ 11时代,我们有一种强大的武器叫move。它允许我们从未使用的对象中窃取资源。std:::只要移动操作为noexcept,当需要增加(或减少)容量时,矢量使用move。

Suppose an exception throws during the move, the previous trunk is not the same as before move happens: resources are stolen, leaving the vector in an broken state. User cannot handle the exception because everything is in an nondeterministic state.

假设在移动过程中抛出一个异常,那么前一个主干与发生移动之前的主干并不相同:资源被窃取,使矢量处于破碎状态。用户无法处理异常,因为一切都处于不确定性状态。

为什么不将向量访问操作符指定为noexcept?

That's why std::vector relies on move constructor to be noexcept.

这就是std::vector依赖于move构造函数的原因。

This is a demostration how client code would rely on noexcept as an interface specification. If later the noexcept requirement is not met, any code previously depends on it will be broken.

这是一个降级客户端代码如何依赖noexcept作为接口规范。如果以后没有满足noexcept要求,以前依赖它的任何代码都将被破坏。


Why not simply mark all function as noexcept?

Short answer: exception safe code is hard to write.

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

Long answer: noexcept set a strict limit to developer who implement the interface. If you want to remove the noexcept from an interface, the client code might be broken like the vector example given above; but if you want to make an interface noexcept, you are free to do it at any time.

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

Thus, only when necessary, mark an interface as noexcept.

因此,只有在必要时,才将接口标记为noexcept。


In the Going Native 2013, Scott Meyers talked about above situation that without noexcept, the sanity of a program will fail.

在《Going Native 2013》中,斯科特•迈耶斯(Scott Meyers)谈到了上述情况:没有别的,程序的健全就会失败。

I also wrote a blog about it: https://xinhuang.github.io/posts/2013-12-31-when-to-use-noexcept-and-when-to-not.html

我也为此写了一篇博客:https://xinhuang.github.io/posts/2013-3-3-3-3-3-3-31 -when- use-no exception -when- no .html

#3


4  

In short, there are functions specified with or without noexcept. It is intended, because they are different. The principle is: a function with undefined behavior specified (e.g. due to improper arguments) should not be with noexcept.

简而言之,有带有或不带有noexcept的函数。它是有意的,因为它们是不同的。它的原理是:一个具有未定义行为的函数(例如,由于不适当的参数)不应该带有noexcept。

This paper explicitly specified these members being without noexcept. Some members of vector were used as examples:

本文明确规定了这些成员是无例外的。以向量的一些成员为例:

Examples of functions having wide contracts would be vector<T>::begin() and vector<T>::at(size_type). Examples of functions not having a wide contract would be vector<T>::front() and vector<T>::operator[](size_type).

函数具有广泛收缩的例子有向量 ::begin()和向量 ::at(size_type)。函数没有广泛收缩的例子有向量 ::front()和向量 :::operator[](size_type)。

See this paper for initial motivation and detailed discussion. The most obvious realistic problem here is testability.

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