Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to enforce move semantics when a vector grows?

I have a std::vector of objects of a certain class A. The class is non-trivial and has copy constructors and move constructors defined.

std::vector<A>  myvec; 

If I fill-up the vector with A objects (using e.g. myvec.push_back(a)), the vector will grow in size, using the copy constructor A( const A&) to instantiate new copies of the elements in the vector.

Can I somehow enforce that the move constructor of class A is beging used instead?

like image 721
Bertwim van Beest Avatar asked Nov 03 '11 21:11

Bertwim van Beest


People also ask

How are Move Semantics implemented?

Move semantics aim to avoid the copying of data from temporary objects by instead stealing the memory location of where the object resides. This behaviour is implemented through the use of a move constructor and move assignment operator that act only on rvalue references.

Does vector have a move constructor?

No. It doesn't call the move constructor. To call move constructor of element you will have to call std::move while pushing to vector itself.

How do you move a vector element?

You can't move elements from one vector to another the way you are thinking about; you will always have to erase the element positions from the first vector. If you want to change all the elements from the first vector into the second and vice versa you can use swap. @R.

Does Emplace_back make a copy?

So you can emplace_back does use the desired constructor to create the element and call copy constructor when it need to grow the storage. You can call reserve with enough capacity upfront to avoid the need to call copy constructor.


2 Answers

You need to inform C++ (specifically std::vector) that your move constructor and destructor does not throw, using noexcept. Then the move constructor will be called when the vector grows.

This is how to declare and implement a move constuctor that is respected by std::vector:

A(A && rhs) noexcept {    std::cout << "i am the move constr" <<std::endl;   ... some code doing the move ...     m_value=std::move(rhs.m_value) ; // etc... } 

If the constructor is not noexcept, std::vector can't use it, since then it can't ensure the exception guarantees demanded by the standard.

For more about what's said in the standard, read C++ Move semantics and Exceptions

Credit to Bo who hinted that it may have to do with exceptions. Also consider Kerrek SB's advice and use emplace_back when possible. It can be faster (but often is not), it can be clearer and more compact, but there are also some pitfalls (especially with non-explicit constructors).

Edit, often the default is what you want: move everything that can be moved, copy the rest. To explicitly ask for that, write

A(A && rhs) = default; 

Doing that, you will get noexcept when possible: Is the default Move constructor defined as noexcept?

Note that early versions of Visual Studio 2015 and older did not support that, even though it supports move semantics.

like image 166
Johan Lundberg Avatar answered Sep 23 '22 10:09

Johan Lundberg


Interestingly, gcc 4.7.2's vector only uses move constructor if both the move constructor and the destructor are noexcept. A simple example:

struct foo {     foo() {}     foo( const foo & ) noexcept { std::cout << "copy\n"; }     foo( foo && ) noexcept { std::cout << "move\n"; }     ~foo() noexcept {} };  int main() {     std::vector< foo > v;     for ( int i = 0; i < 3; ++i ) v.emplace_back(); } 

This outputs the expected:

move move move 

However, when I remove noexcept from ~foo(), the result is different:

copy copy copy 

I guess this also answers this question.

like image 40
Nikola Benes Avatar answered Sep 19 '22 10:09

Nikola Benes