Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moving temporary objects into a vector

#include <iostream>
#include <utility>
#include <vector>

int i = 0;
struct A
{
    A() : j( ++i )
    {
        std::cout<<"constructor "<<j<<std::endl;
    }
    A( const A & c) : j(c.j)
    {
        std::cout<<"copy "<<j<<std::endl;
    }
    A( const A && c) : j(c.j)
    {
        std::cout<<"move "<<j<<std::endl;
    }
    ~A()
    {
        std::cout<<"destructor "<<j<<std::endl;
    }

    int j;
};

typedef std::vector< A > vec;

void foo( vec & v )
{
    v.push_back( std::move( A() ) );
}

int main()
{
    vec v;

    foo( v );
    foo( v );
}

The example above produces next output :

constructor 1
move 1
destructor 1
constructor 2
move 2
move 1
destructor 1
destructor 2
destructor 1
destructor 2

Questions :

  1. Why is the 1st destructor executed (but it isn't executed for the 2nd object)?
  2. Why is move of the 2nd object, executed before the move of the 1st object?
  3. Why are at the end two destructors for each object executed?

PS I just checked, and the objects are indeed placed as expected (the 1st goes to the position 0 in the vector, and the 2nd goes to the position 1 in the vector)

PPS If it matters, I am using gcc 4.3, and I compile the program like this :

g++ n1.cpp -Wall -Wextra -pedantic -ansi -std=c++0x -O3
like image 438
BЈовић Avatar asked Oct 06 '11 08:10

BЈовић


People also ask

How do you push an object into a vector?

vector::push_back() push_back() function is used to push elements into a vector from the back. The new value is inserted into the vector at the end, after the current last element and the container size is increased by 1.

What can be stored in a vector?

vector will store whatever your type contains in contiguous memory. So yes, if that's an array or a tuple , or probably even better, a custom type, it will avoid indirection. Performance-wise, as always, you have to measure it.

Is std::vector movable?

std::vector typically stores some pointers plus the allocator. When move-constructing a std::vector , only pointers and the allocator have to be moved. The elements don't need to be touched.

How do you move one vector to another?

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.


3 Answers

I've slightly recoded your example:

#include <iostream>
#include <utility>
#include <vector>

int i = 0;
struct A
{
    A() : j( ++i )
    {
        std::cout<<"constructor "<<j<<std::endl;
    }
    A( const A & c) : j(c.j)
    {
        std::cout<<"copy "<<j<<std::endl;
    }
    A( A && c) : j(c.j)
    {
        std::cout<<"move "<<j<<std::endl;
    }
    ~A()
    {
        std::cout<<"destructor "<<j<<std::endl;
    }

    int j;
};

typedef std::vector< A > vec;

void foo( vec & v )
{
    v.push_back( A() );
}

int main()
{
    vec v;
    std::cout << "A\n";
    foo( v );
    std::cout << "B\n";
    foo( v );
    std::cout << "C\n";
}
  1. I've removed the const from the move constructor.
  2. I've removed the std::move from the push_back (it is superfluous).
  3. I've inserted markers between the calls to foo.

For me this prints out similar to your code:

A
constructor 1
move 1
destructor 1
B
constructor 2
move 2
copy 1
destructor 1
destructor 2   // 1
C
destructor 2
destructor 1
  1. Why is the 1st destructor executed (but it isn't executed for the 2nd object)?

The 2nd destructor is executed for the 2nd object at the line marked // 1. This is the destruction of the temporary A() at the end of the second call to push_back.

  1. Why is move of the 2nd object, executed before the move of the 1st object?

Note: For me the 1st object is copied, not moved, more about that below.

Answer: Exception safety.

Explanation: During this push_back the vector discovers it has a full buffer (of one) and needs to create a new buffer with room for two. It creates the new buffer. Then moves the second object into that buffer (at the end of it). If that construction throws an exception, the original buffer is still intact and the vector remains unchanged. Otherwise the elements are moved or copied from the first buffer to the second buffer (thus move/copying the first element second).

If A has a noexcept move constructor a move will be used to move it from the old buffer to the new. However if the move constructor is not noexcept then a copy will be used. This is again for exception safety. If the movement from the old buffer to the new can fail, then the old buffer must be left intact so that the vector can be restored to its original state.

If I add noexcept to your move constructor:

A( A && c) noexcept : j(c.j)
{
    std::cout<<"move "<<j<<std::endl;
}

Then my output is:

A
constructor 1
move 1
destructor 1
B
constructor 2
move 2
move 1
destructor 1  // 2
destructor 2
C
destructor 2
destructor 1

Note the line marked // 2 is the destruction of the first element from the old buffer, after it has been move constructed into the new buffer.

  1. Why are at the end two destructors for each object executed?

This is marking the destruction of the vector and thus the destruction of each of the vector's elements.

like image 79
Howard Hinnant Avatar answered Nov 08 '22 21:11

Howard Hinnant


Judicious use of reserve solves half your problem: http://ideone.com/5Lya6 by reducing the number of unexpected moves (that you don't explicitely request)

Also don't forget that the temp's destructor will still fire after having being moved into the vector. This is why you have to make sure that the temp remains in sane, destructible state even after the move assignment/construction.

#include <iostream>
#include <utility>
#include <vector>

int i = 0;
struct A
{
    A() : j( ++i )
    {
        std::cout<<"constructor "<<j<<std::endl;
    }
    A( const A & c) : j(c.j)
    {
        std::cout<<"copy "<<j<<std::endl;
    }
    A( const A && c) : j(c.j)
    {
        std::cout<<"move "<<j<<std::endl;
    }
    ~A()
    {
        std::cout<<"destructor "<<j<<std::endl;
    }

    int j;
};

typedef std::vector< A > vec;

void foo( vec & v )
{
    v.push_back( std::move( A() ) );
}

int main()
{
    vec v;
    v.reserve(2);

    foo( v );
    foo( v );
}
like image 36
sehe Avatar answered Nov 08 '22 21:11

sehe


The vector is increasing its capacity and moving the internal elements during the call to push_back.

like image 37
dalle Avatar answered Nov 08 '22 20:11

dalle