Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does std::vector use the move constructor although declared as noexcept(false)

Wherever I read in the internet, it is strongly adviced that if I want my class to be working well with std::vector (i.e. move semantics from my class were used by std::vector) I should delcare move constructor as 'noexcept' ( or noexcept(true) ).

Why did std::vector use it even though I marked it noexcept(false) as an experiment?

#include <iostream>
#include <vector>
using std::cout;

struct T
{
    T() { cout <<"T()\n"; }

    T(const T&) { cout <<"T(const T&)\n"; }

    T& operator= (const T&)
    { cout <<"T& operator= (const T&)\n"; return *this; }

    ~T() { cout << "~T()\n"; }

    T& operator=(T&&) noexcept(false)
    { cout <<"T& operator=(T&&)\n"; return *this; }

    T(T&&) noexcept(false)
    { cout << "T(T&&)\n"; }
};

int main()
{
    std::vector<T> t_vec;
    t_vec.push_back(T());
}

output:

T()
T(T&&)
~T()
~T()

Why ? What did I do wrong ?

Compiled on gcc 4.8.2 with CXX_FLAGS set to:

--std=c++11 -O0 -fno-elide-constructors
like image 304
Grzegorz Wolszczak Avatar asked Oct 06 '14 20:10

Grzegorz Wolszczak


2 Answers

You did nothing wrong.

You just wrongly thought push_back had to avoid a throwing move-ctor: It does not, at least for constructing the new element.

The only place where throwing move-ctors / move-assignments must be shunned is on re-allocation of the vector, to avoid having half the elements moved, and the rest in their original places.

The function has the strong exception-safety guarantee:

Either the operation succeeds, or it fails and nothing has changed.

like image 80
Deduplicator Avatar answered Nov 08 '22 20:11

Deduplicator


If vector::push_back needs to reallocate its storage it first allocates new memory, then move constructs the new element into the last position. If that throws the new memory is deallocated, and nothing has changed, you get the strong exception-safety guarantee even if the move constructor can throw.

If it doesn't throw, the existing elements are transferred from the original storage to the new storage, and here is where the noexcept specification of the move constructor matters. If moving might throw and the type is CopyConstructible then the existing elements will be copied instead of moved.

But in your test you're only looking at how the new element is inserted into the vector, and it is always OK to use a throwing constructor for that step.

like image 29
Jonathan Wakely Avatar answered Nov 08 '22 19:11

Jonathan Wakely