Zah
Zah

Reputation: 6794

Appending an item to a vector copies all previous items

In the following code, I would expect that the copy constructor of A is never called, as the items should be created directly at the vector with emplace_back and then the total result should be return-value-optimized.

This seems to be the case. However, every time I append to the vector, all previous items are being copied as a result of the emplace_back call. Why is this happening?

#include <iostream>
#include <vector>
#include <string>

using namespace std;


static int _id = 0;

class A{
public:
    A(): name(_id++){
        cout << "Created" << this->name << endl;
    }

    A(const A& other): name(other.name){
        cout << "Copied" << this->name << endl;
    }

    A(const A&& other): name(other.name){
        cout << "Moved" << this->name << endl;
    }

    ~A(){
        cout << "Deleted"<< this->name <<endl;
    }

private:
    int name;
};


vector<A>  f2(){
    cout << "Entering f2" << endl;
    auto ret = vector<A>();
    for (int i = 0; i < 3; i++){
        //auto obj = A();
        cout << "Adding obj" << endl;
        ret.emplace_back();
        cout << "Added obj" << endl;
    }
    cout << "Returning" << endl;
    return ret;
}

int main()
{
    {
        auto c = f2();
        cout << "Exiting stack" << endl;
    }
    return 0;
}

I compile it with gcc 4.8.4 using any of:

gcc main.cpp -std=c++11
gcc main.cpp -std=c++11 -O3

The output is:

Entering f2
Adding obj
Created0
Added obj
Adding obj
Created1
Copied0
Deleted0
Added obj
Adding obj
Created2
Copied0
Copied1
Deleted0
Deleted1
Added obj
Returning
Exiting stack
Deleted0
Deleted1
Deleted2

And I would expect to not have copies, something like:

Entering f2
Adding obj
Created0
Added obj
Adding obj
Created1
Added obj
Adding obj
Created2
Added obj
Returning
Exiting stack
Deleted0
Deleted1
Deleted2

Changing emplace_back with push_back doesn't cure this, and adds additional moves.

Upvotes: 2

Views: 151

Answers (2)

Andy Jewell
Andy Jewell

Reputation: 455

Also, if you mark your move constructor noexcept, then when the vector grows, it will move rather than copying.

See also How to enforce move semantics when a vector grows?

Upvotes: 3

Violet Giraffe
Violet Giraffe

Reputation: 33579

Vector will re-allocate its memory block when it runs out of storage space, and copy the old objects into their new places. You can significantly reduce the number of such re-allocations by calling vector.reserve(<expected number of items>);
Also worth noting is that vector guarantees amortized constant push_back complexity. It means that as you keep adding items, reallocations will occur less and less often, so it's usually not a concern.

Or, if you just need some sort of a container and don't need it to be allocated in memory contiguously, consider using a different container. Like std::deque.

Upvotes: 4

Related Questions