admin管理员组

文章数量:1355694

#include <iostream>
#include <vector>

class Car{
  public:
    int weight;

    Car(int weight): weight(weight){
      
    };

    Car(const Car& other){
        std::cout<<"copied!"<<std::endl;
        this->weight=other.weight;
    }
};

int main() {
    std::vector<Car> vec;
    vec.push_back(Car(1));
    return 0;
}

In the code above, somehow when I push Car(1) onto vec, the copy constructor in the Car class is called. Why is the copy constructor called? I'm not copying anything here.

#include <iostream>
#include <vector>

class Car{
  public:
    int weight;

    Car(int weight): weight(weight){
      
    };

    Car(const Car& other){
        std::cout<<"copied!"<<std::endl;
        this->weight=other.weight;
    }
};

int main() {
    std::vector<Car> vec;
    vec.push_back(Car(1));
    return 0;
}

In the code above, somehow when I push Car(1) onto vec, the copy constructor in the Car class is called. Why is the copy constructor called? I'm not copying anything here.

Share Improve this question edited Mar 29 at 20:33 Chris 36.9k6 gold badges33 silver badges55 bronze badges asked Mar 29 at 19:59 simply lemonsimply lemon 1311 silver badge5 bronze badges 5
  • 5 You are copying the temporary Car object that you constructed here Car(1). If you want to avoid this copy use emplace_back, e.g. vec.emplace_back(1). emplace_back will call any available constructor and contruct the object being added directly in the vector, without any copying. – john Commented Mar 29 at 20:07
  • I'm not sure, but maybe because vector contains Car objects and not pointers or references on Car objects. Because of that, every time you push element into vector, it will make copy of that element and hence call copy constructor. – Milos Stojanovic Commented Mar 29 at 20:07
  • 1 See std::vector::push_back() and it should be clear that it copies its argument. – Jesper Juhl Commented Mar 29 at 20:13
  • Btw; this->weight=other.weight; can simply be weight=other.weight;. There's no need to explicitly dereference this. – Jesper Juhl Commented Mar 29 at 20:15
  • 1 @JesperJuhl or even just using a member initializer list. – Chris Commented Mar 30 at 0:35
Add a comment  | 

3 Answers 3

Reset to default 10

You are copying since your Car struct does not implement move semantics. The copy is from the temporary you built with Car(1) to the object created in the vector. Using emplace_back to directly construct the object in the vector without the temporary object will prevent this.

#include <iostream>
#include <vector>

struct Car {
    int weight;

    Car(int weight) : weight(weight) { }

    Car(const Car& other) : weight(other.weight) {
        std::cout << "copied!\n";
    }
};

int main() {
    std::vector<Car> vec;
    vec.emplace_back(1);
}

Note that even using emplace_back you may still see copy constructors called if the vector has to reallocate.

A bit of elaboration on the topic due to the fact that you labeled your question with move-semantics tag. My assumption is that you expected your code to expose those semantics here and hence your surprise here.

As you probably know, in C++ 11 all containers have been updated with support for moving objects into their storage instead of copying. Now, for most functions designed to insert a new value into a container we have something like:

void push_back( const T& value );
void push_back( T&& value );

Which of these is selected, depends on the parameter type and the ability of T to be copy- and/or move-constructed. And this is where your code gets it wrong.

Indeed, vec.push_back(Car(1)); should move-insert the value to the container (Car(1) is a temporary bound to an rvalue, so push_back( T&& value ); is definitely the matching overload). Why this is not happenening is because your class does not provide move semantics.

By default, the compiler generates all set of constructors, assignment operators and a destructor (depends on the members stored inside a class and their copy/move abilities), but when a user-defined versions appear, this is no longer the case.

The wording you should be looking at can be found here, for example and goes as follows:

Implicitly-declared move constructor

If no user-defined move constructors are provided for a class type, and all of the following is true:

  • there are no user-declared copy constructors;

  • there are no user-declared copy assignment operators;

  • there are no user-declared move assignment operators;

  • there is no user-declared destructor.

Then the compiler will declare a move constructor as a non-explicit inline public member of its class with the signature T::T(T&&).

And there you have it - since you defined your own copy constructor, the first requirement is not met, so you need to write your own move constructor if you want it (and you most definitely should do the same for relevant assignment operators as well).

If you haven't come accross terms "Rule of Three (Five since C++ 11)", take a look here.

After this quite long elaborate for this simple problem, let's finally fix your code:

class Car{
  public:
    int weight;

    Car(int weight): weight(weight){
      
    };

    Car(Car&& other){
        std::cout<<"moved!"<<std::endl;
        this->weight=other.weight;
    }

    Car(const Car& other){
        std::cout<<"copied!"<<std::endl;
        this->weight=other.weight;
    }
};

We can then run your code and observe the right result you expected:

Success #stdin #stdout 0.01s 5288KB
moved!

A temporary `Car(1)` is being pushed into the vector, the copy constructor is called. C++ adds the item to the vector using the copy constructor because your class lacks a move constructor. The vector must create a copy in order to store it, even though it appears that you are not copying.

本文标签: cCopy constructor called when pushing object onto stdvectorStack Overflow