1

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```
1
  • 2
    The difference between copy/move is not related to whether or not the copy is deep. For properly designed types there shouldn't be any semantic difference (except the state of the moved-from object) when the vector uses one over the other. std::vector will internally use the cheaper move when that is possible while maintaining exception safety guarantees. Commented Sep 23, 2023 at 0:44

1 Answer 1

4

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.

Sign up to request clarification or add additional context in comments.

3 Comments

If this is the case and that push_back deep copies every single lvalue, doesn't this make the time complexity very very bad? Say I have vector<vector<int>>, the time complexity for appending several vectors would be O(n*m), where n is number of vector, m is max allowed length of the vectors?
I just hear that people are saying "time complexity for push_back is O(1)" but I think that's deceptive
@qwert - push_back is O(1), because it copies one object. It doesn't say that all objects are equally expensive to copy.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.