Search code examples
c++move

vector::push_back using std::move for internal reallocations


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


Solution

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