A type X
has a non-trivial move-constructor, which is not declared noexcept (ie. it can throw):
#include <iostream>
#include <vector>
#include <memory>
struct X {
X()=default;
X(X&&) { std::cout << "X move constructed\n"; } // not 'noexcept'
X(const X&) { std::cout << "X copy constructed\n"; }
};
struct test {
X x;
};
int main()
{
static_assert(std::is_nothrow_move_constructible_v<X> == false);
static_assert(std::is_nothrow_move_constructible_v<test> == false);
std::vector<test> v(1);
v.push_back(test{}); // internal object re-allocation, uses copy-constructor
}
When calling push_back
on a vector<test>
, internal reallocations cause existing objects to be copied, which is expected.
The output is:
X move constructed
X copy constructed
Apparently, the second line is related to internal object reallocation.
Now an unrelated std::unique_ptr<int>
is added to test
:
struct test {
std::unique_ptr<int> up;
X x;
};
The output becomes:
X move constructed
X move constructed
Why is internal reallocation using the move-constructor this time ?
Technically, X(X&&)
can still throw an exception; wouldn't that violate the strong exception guarantee of push_back
?
compiler is gcc 11.2.1 and/or clang 12.0.1
According to [vector.modifiers]/2 if the element type of std::vector
is not copy-insertable and not nothrow-move-constructible, then an exception thrown from a move constructor of the element type results in an unspecified state of the container.
std::vector
must prefer the copy constructor if the type isn't nothrow-movable, so that the exception guarantees can be preserved, but if that isn't possible, it is impossible to give strong exception guarantees.