Consider the following program:
#include <vector>
#include <iostream>
class A {
int x;
public:
A(int n) noexcept : x(n) { std::cout << "ctor with value\n"; }
A(const A& other) noexcept : x(other.x) { std::cout << "copy ctor\n"; }
A(A&& other) noexcept : x(other.x) { std::cout << "move ctor\n"; }
~A() { std::cout << "dtor\n"; } // (*)
};
int main()
{
std::vector<A> v;
v.emplace_back(123);
v.emplace_back(456);
}
If I run the program, I get (GodBolt):
ctor with value
ctor with value
move ctor
dtor
dtor
dtor
... which is in line with what I would expect. However, if on line (*)
I mark the destructor as potentially throwing, I then get :
ctor with value
ctor with value
copy ctor
dtor
dtor
dtor
... i.e. the copy ctor is used instead of the move ctor. Why is this the case? It doesn't seem copying prevents destructions that moving would necessitate.
Related questions:
std::vector
prefers to offer you a "strong exception guarantee".(Thanks goes to Jonathan Wakely, @davidbak, @Caleth for links & explanations)
Suppose std::vector
were to use move construction in your case; and suppose that an exception were to be thrown during vector-resizing, by one of the A::~A
calls. In that case, you would have an unusable std::vector
, partially moved.
On the other hand, if std::vector
performs copy construction, and an exception occurs in one of the destructors - it can simply ditch the new copy, and your vector will be in the same state it was before the resizing. That is the "strong exception guarantee" for the std::vector
object.
The standard library designers chose to prefer this guarantee over optimizing the performance of vector resizing.
This had been reported as an issue/defect with the standard library (LWG 2116) - but after some discussion, it was decided to keep the current behavior as per the above consideration.
See also Arthur O'Dwyr's post: A "Pick any two" triangle for std::vector
.