Why std::vector
's operator[]
, front
and back
member functions are not specified as noexcept
?
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.
noexcept
could actually unwind. However, the practical considerations of code generation and the fact that such debug modes turn undefined behavior into defined behavior (throw an exception) make this a non-viable approach to writing a C++ implementation. If a function is noexcept
, the caller might well be missing unwind tables, so trying to unwind would crash or send execution into never-never land, and that kind of defeats the point of throwing an exception. –
Braxton noexcept
and takes a T
, then that function is stating that it cannot/must not fail for any value of T
, after all. Building on the examples given, a vector::operator[](i) noexcept
might perform bounds checking like vector::at()
, and return a sentinel value for any i >= size()
instead of throwing an exception. –
Bondie 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.
wide contract
from the same paper: A wide contract for a function or operation does not specify any undefined behavior. Such a contract has no preconditions: A function with a wide contract places no additional runtime constraints on its arguments, on any object state, nor on any external global state. –
Arthur © 2022 - 2024 — McMap. All rights reserved.