Why std::vector
's operator[]
, front
and back
member functions are not specified as noexcept
?
Why vector access operators are not specified as noexcept?
6k views Asked by lizarisk AtThere are 3 answers
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.
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.
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
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()
andvector<T>::at(size_type)
. Examples of functions not having a wide contract would bevector<T>::front()
andvector<T>::operator[](size_type)
.
See this paper for initial motivation and detailed discussion. The most obvious realistic problem here is testability.
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 notnoexcept
, 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) andnumeric_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 callfront
orback
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-mandatednoexcept
on those functions makes this impossible.