Copy constructor called too many times

Viewed 124

I have the following code:

#include <iostream>
#include <vector>
using namespace std;


int counter = 0;
struct vecobj
{
   int elem;
   vecobj(){ }
   vecobj(const vecobj& rhs)
   {
    counter++;
    elem = rhs.elem;
    
    
   }
};

vector<vecobj> objvec;

int main()
{
    vecobj obj1;
    obj1.elem = 1;
    objvec.push_back(obj1);
    objvec.push_back(obj1);
    objvec.push_back(obj1);
    std::cout <<"copy constructor called "<< counter <<" times\n";
}

My question is that when I push an object into the vector objvec, I get the copy constructor called 6 times. While I have pushed only three times. I have verified this with the debugger. I am using visual studio 2017 Community Edition. Is it a bug with STL? I think a related question does not address the problem fully. No where is it documented that the vector memory has to be reserved before pushing onto it.The pushing of object calling the copy constructor too many (fibonacci(n)) times can have unintended side effects. I think STL code needs to be changed for the same.

3 Answers

Normally std::vector implementation have a capacity and a size variables. To track the current capacity means maximum number of elements that can be created without requesting a bigger heap block. 1size1 tracks the current size of std::vector.

When we push_back an element and size becomes greater than capacity then program will ask for new heap and copy all the elements to new location.

Capacity increases in power of 2. like initially space for 1 object/variable will be allocated, if application will need more then space for 2 objects/variables will be allocated, then for 4 and so on.

  1. Initially first copy happens when you push_back first element. (1 Copy)
  2. When you push_back second element then a new memory is allocated and the new element as well as the already push_backed element is copied to new location. (2 Copy)
  3. Similarly third time three copies will happen.

All of this is implementation dependent. Standard don't impose that std::vector should behave like this only.

To save the copies you can reserve the enough memory before you make any push_back call.

You already have the trivial 3 copies here:

objvec.push_back(obj1);
objvec.push_back(obj1);
objvec.push_back(obj1);

Then you might check capacity (depend of implementation), here I have:

std::cout << "capacity: " << objvec.capacity() << std::endl; // 0
objvec.push_back(obj1);
std::cout << "capacity: " << objvec.capacity() << std::endl; // 1
objvec.push_back(obj1);
std::cout << "capacity: " << objvec.capacity() << std::endl; // 2
objvec.push_back(obj1);
std::cout << "capacity: " << objvec.capacity() << std::endl; // 4

Each time capacity changes, reallocation happens, and we have to transfer data from old buffer to the new one. so copy (or move).

You might reduce copy by using correct capacity from the beginning:

vecobj obj1;
obj1.elem = 1;

objvec.reserve(3);
objvec.push_back(obj1);
objvec.push_back(obj1);
objvec.push_back(obj1);

Demo

Lets put moves aside (your elements cannot be moved), then push_back copies at least one element to insert it into the vector. If the vectors capacity is not enough to hold the additional element, then the vector has to resize. While doing that it copies all elements. Typical resize strategies allocate space for elements by factors of two. So this is possibly what happens:

 vecobj obj1;               // size 0 capacity 0
obj1.elem = 1;
objvec.push_back(obj1);     // size 1 capacity 1   -> 1 copy to insert
objvec.push_back(obj1);     // size 2 capacity 2   -> 1 copy to insert 1 to reallocate
objvec.push_back(obj1);     // size 3 capacity 4   -> 1 copy to insert 2 to reallocate

Which makes a total of 6 copies.

Related