c++destructorcopy-constructormove-constructor

c++ when do vector push_back deep copy objects?


I created a vector and used push_back to put several node objects into it. However, I can't predict when its going to use the move constructor or copy constructor.

Is there any pattern to when push_back use copy constructor or move constructor? c++ reference says that it'll sometimes copy and sometimes move, but never went into detail as to when they do what.

#include <iostream>
#include <vector>
#include <unordered_set>
#include <set>
#include <unordered_map>
#include <map>
#include <queue>
using namespace std;
struct Node {
    int val;
    
    Node(int val) : val(val) {
        cout<<"created object" << val<<endl;
    }
    
    Node(const Node& m) : val(m.val) {
        cout<<"copy constructor is called on value " << m.val << endl;
    }
    
    ~Node() {
        cout<<"destroyed val" << val<<endl;
    }


    Node(Node&& other) noexcept 
        : val(other.val) {
            cout<<"moved val " << other.val << endl;
    }


};


void f(vector<Node>& a) {
    cout<<"______________________"<<endl;
    Node tmp(12);
    cout<<"12 established"<<endl;
    a.push_back(tmp);
    cout<<"a pushed back 12"<<endl;
    a.push_back(Node(14));
    
    cout<<"a pushed back tmp obj 14"<<endl;
    
    tmp.val+=5;
    
    cout<<"increased tmp.val"<<endl;
    cout<<tmp.val<<endl;
    cout<<a[1].val<<endl;
    cout<<"two prints"<<endl;
    cout<<"_______________"<<endl;
    cout<<"end of f"<<endl;
    // return a;
}

int main() {
    
    vector<Node> a = {Node(125)}; //copied since initialized temp var.
    a.reserve(4000);
    cout<<"start of f"<<endl;
    f(a);

    
    cout<<"program ended"<<endl;


    //noteiced: Copy constructor called upon local variable (12) that the vector knows will not stay with it --- and belongs to local scope.
    //copy constructor not called upon temporary variable that soon belonged to vector (14).
    //same thing with std::queue, std::stack, and many others.
//but it this a pattern or a coincidence?
}



Output:

copy constructor is called on value 125
destroyed val125
moved val 125
destroyed val125
start of f
______________________
created object12
12 established
copy constructor is called on value 12
a pushed back 12
created object14
moved val 14
destroyed val14
a pushed back tmp obj 14
increased tmp.val
17
12
two prints
_______________
end of f
destroyed val17
program ended
destroyed val125
destroyed val12
destroyed val14```

Solution

  • The rules are very simple.

    std::vector::push_back has two overloads.

    The first overload takes a const T & parameter. Invoking that overload uses the object's copy constructor.

    The second overload takes a T && parameter. Invoking that overload uses the object's move constructor.

    Note that both overloads may end up reallocating the vector, which will trigger a whole bunch of moves, which is besides the point. I haven't mapped out all of your code's diagnostic output, but it's also possible that you're logging moves due to reallocation and this further muddles the issue (although I see that you used reserve() to get rid of this, the reserve() is on a non-empty vector, so its sole existing value gets moved, and you might be getting confused by that, too).

    And all of this assumes that T implements move semantics. If it doesn't, it's copies all the way.

    So what you have to do is simply determine whether a given particular invocation of push_back() passes an lvalue or an rvalue.

        a.push_back(tmp);
    

    tmp is obviously an lvalue. This will end up invoking the copy constructor.

        a.push_back(Node(14));
    

    This parameter is an rvalue. This will end up invoking the move constructor.

    You can work out the rest of the invocations using the same principle.