henryyao
henryyao

Reputation: 1818

why the copy-constructor is called twice when doing a vector.push_back

I have a Class Child, and a simple vector push back implementation like this:

#include <stdio.h>
#include <vector>
#include <iostream>

class Child 
{
    public:
        Child();
        Child(const Child &item);
        ~Child();
};

Child::Child()
{
    std::cout<<"Constructing Child\n";
}

Child::Child(const Child &item)
{
    std::cout<<"Copy-Constructing Child\n";
}

Child::~Child()
{
    std::cout<<"Destructing Child\n";
}

int main()
{
    std::vector<Child> v;
    Child Item1;
    v.push_back(Item1);
}

Right after v.push_back( Item1 ); is called, i have the following outputs:

Constructing Child
Copy-Constructing Child
Copy-Constructing Child
Destructing Child

I was expecting the Copy-Constructing Child only appears once. Why is the copy-constructor called twice?

Upvotes: 2

Views: 1445

Answers (2)

Moby Disk
Moby Disk

Reputation: 3861

This is happening because you are using an old compiler. Sorry if that isn't a really exciting conclusion. :-( No one else can replicate the problem, even on newer Microsoft compilers. While Richard Hodges is correct when he says that a compiler is free to make copies as it sees fit, modern compilers do their best to avoid them. In the specific case you list, a decent compiler should not be making a copy, and if you are concerned about performance you should consider upgrading to a newer version.

Upvotes: 1

Richard Hodges
Richard Hodges

Reputation: 69912

Official answer: In the case of your class, std::vector is free to make as many copies as it likes since there is no limit specified in the standard.

What is probably happening: a copy is being made when passing the object as a parameter and again when the vector is resized internally.

Isn't this inefficient? Yes

How do I improve it?

Either remove the custom destructor and copy constructor or define a custom move constructor and copy operator. The former case allows the compiler to create its own move operations, the latter provides them.

Once the compiler can deduce that your class is move-aware, std::vector's operations become a lot more efficient and you will see exactly one copy (because you are forcing the compiler to make one by naming the Child called Item1)

Edit: consider this example

#include <stdio.h>
#include <vector>
#include <iostream>

class Child
{
public:
    Child();
    Child(const Child &item);
    Child(Child&& item);
    Child& operator=(const Child &item);
    Child& operator=(Child&& item);
    ~Child();

private:
    std::string name() const {
        return std::string { _zombie ? "zombie" : "child" };
    }
    bool _zombie = false;
};

Child::Child()
{
    std::cout << "constructing " << name() << "\n";
}

Child::Child(const Child &item)
{
    std::cout << "copy-constructing " << name() << "\n";
}

Child::Child(Child &&item)
{
    std::cout << "move-constructing " << name() << "\n";
    item._zombie = true;
}

Child& Child::operator=(const Child &item)
{
    _zombie = false;
    std::cout << "assigning " << name() << "\n";
    return *this;
}

Child& Child::operator=(Child &&item)
{
    item._zombie = true;
    _zombie = false;
    std::cout << "move-assigning " << name() << "\n";
    return *this;
}

Child::~Child()
{
    std::cout << "destructing " << name() << "\n";
}

using namespace std;

int main(int argc, const char * argv[])
{
    {
        std::vector<Child> v;
        Child item1;
        v.push_back(item1);
    }
    cout << endl;

    {
        std::vector<Child> v;
        v.push_back(Child{});
    }
    cout << endl;

    {
        std::vector<Child> v;
        Child item1;
        v.push_back(std::move(item1));
    }
    cout << endl;
    return 0;
}

sample output:

constructing child
copy-constructing child
destructing child
destructing child

constructing child
move-constructing child
destructing zombie
destructing child

constructing child
move-constructing child
destructing zombie
destructing child

Upvotes: 1

Related Questions