Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why vector access operators are not specified as noexcept?

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

like image 705
lizarisk Avatar asked Dec 11 '13 10:12

lizarisk


3 Answers

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.

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).

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.

like image 104
Sebastian Redl Avatar answered Sep 22 '22 18:09

Sebastian Redl


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

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 since C++11) all existing elements to the new trunk, and then add the new element to the back.

Use copy constructor to expand a vector

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.

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.

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.

Use move constructor to expand a vector

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

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.


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.

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


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

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

like image 24
Xin Huang Avatar answered Sep 22 '22 18:09

Xin Huang


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.

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).

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

like image 21
FrankHB Avatar answered Sep 18 '22 18:09

FrankHB