Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why the copy constructor is called 25 times, while the insertion loop iterates only 10 times? [duplicate]

I am wondering why in the following C++ code the copy constructor is called 25 times for 10 iterations?

If it was 10 then OK 10/10 = 1, or 20/10 = 2, or 30/10 = 3, but 25/10 = 2.5? What does the .5 here mean?

Header:

class Person
{
public:
    Person(std::string name, int age);
    Person(const Person &person);

    const std::string &getName() const;
    int getAge() const;

private:
    std::string name;
    int age;
};

Source:

Person::Person(string name, int age) : name(std::move(name)), age(age)
{}

Person::Person(const Person &person)
{
    this->name = person.name;
    this->age = person.age;
    static int count = 0;
    count++;
    cout << ">>Copy-Person::Person(Person &person) " << count << endl;
}

const string &Person::getName() const
{
    return name;
}

int Person::getAge() const
{
    return age;
}

Usage:

int main()
{
    vector<Person> persons;

    for (int i = 0; i < 10; ++i)
    {
        Person person(to_string(i + 1), i);
        persons.push_back(person);
    }
    cout << "-----------------------------------------------" << endl;
    for (Person &person : persons)
    {
        cout << "name = " << person.getName() << " age = " << person.getAge() << endl;
    }
    return 0;
}

Output:

>>Copy-Person::Person(Person &person) 1
>>Copy-Person::Person(Person &person) 2
>>Copy-Person::Person(Person &person) 3
>>Copy-Person::Person(Person &person) 4
>>Copy-Person::Person(Person &person) 5
>>Copy-Person::Person(Person &person) 6
>>Copy-Person::Person(Person &person) 7
>>Copy-Person::Person(Person &person) 8
>>Copy-Person::Person(Person &person) 9
>>Copy-Person::Person(Person &person) 10
>>Copy-Person::Person(Person &person) 11
>>Copy-Person::Person(Person &person) 12
>>Copy-Person::Person(Person &person) 13
>>Copy-Person::Person(Person &person) 14
>>Copy-Person::Person(Person &person) 15
>>Copy-Person::Person(Person &person) 16
>>Copy-Person::Person(Person &person) 17
>>Copy-Person::Person(Person &person) 18
>>Copy-Person::Person(Person &person) 19
>>Copy-Person::Person(Person &person) 20
>>Copy-Person::Person(Person &person) 21
>>Copy-Person::Person(Person &person) 22
>>Copy-Person::Person(Person &person) 23
>>Copy-Person::Person(Person &person) 24
>>Copy-Person::Person(Person &person) 25
-----------------------------------------------
name = 1 age = 0
name = 2 age = 1
name = 3 age = 2
name = 4 age = 3
name = 5 age = 4
name = 6 age = 5
name = 7 age = 6
name = 8 age = 7
name = 9 age = 8
name = 10 age = 9
like image 674
Bahramdun Adil Avatar asked Sep 21 '17 09:09

Bahramdun Adil


People also ask

What are the three times the copy constructor is called automatically?

In C++, a Copy Constructor may be called for the following cases: 1) When an object of the class is returned by value. 2) When an object of the class is passed (to a function) by value as an argument. 3) When an object is constructed based on another object of the same class.

How many copy constructors are there?

In such cases, the compiler does not create one. Hence, there is always one copy constructor that is either defined by the user or by the system.

Can you have multiple copy constructors?

A class can have multiple copy constructors, e.g. both T::T(const T&) and T::T(T&). If some user-defined copy constructors are present, the user may still force the generation of the implicitly declared copy constructor with the keyword default .

Why is copy constructor called reference?

A copy constructor defines what copying means,So if we pass an object only (we will be passing the copy of that object) but to create the copy we will need a copy constructor, Hence it leads to infinite recursion. So, A copy constructor must have a reference as an argument.


3 Answers

You are not reserving any memory for your persons vector. This means that when persons.size() == persons.capacity() during a push_back, the vector will allocate a new bigger buffer on the heap and copy every element to it. This is why you see more copies than expected.

If you write...

persons.reserve(10); 

...before the loop, you will not see any "extra" copy.

live example on wandbox


Note that you can avoid the copies altogheter by using both std::vector::emplace_back and std::vector::reserve:

for (int i = 0; i < 10; ++i)
{
    persons.emplace_back(to_string(i + 1), i);
}

This will only print:

name = 1 age = 0

name = 2 age = 1

name = 3 age = 2

name = 4 age = 3

name = 5 age = 4

name = 6 age = 5

name = 7 age = 6

name = 8 age = 7

name = 9 age = 8

name = 10 age = 9

live example on wandbox

like image 109
Vittorio Romeo Avatar answered Nov 15 '22 05:11

Vittorio Romeo


When the new size() > capacity() of the vector, reallocation happens. All the elements will be copied to the new internal storage, then the copy constructor will be called at the current elements' number of times. The details about how the capacity is increased depends on the implementation, it seems the implementation you're using just double the capacity for every reallocation. so

#iterator current size  capacity  times of the copy (for reallocatioin + for push_back)
1         0             0         0 + 1             
2         1             1         1 + 1             
3         2             2         2 + 1             
4         3             4         0 + 1             
5         4             4         4 + 1             
6         5             8         0 + 1             
7         6             8         0 + 1             
8         7             8         0 + 1             
9         8             8         8 + 1             
10        9             16        0 + 1             

That's why you got the result of 25 times.

As @VittorioRomeo explained, you can use std::vector::reserve to avoid reallocation.

like image 26
songyuanyao Avatar answered Nov 15 '22 07:11

songyuanyao


When std::vector::size() reaches std::vector::capacity(), std::vector will make room for more objects, allocating a new larger buffer with bigger capacity, and copying the previously stored objects into the new buffer.
This triggers new copy constructor calls for your Person class (I tried your code with VS2015, and I got 35 copy constructor calls).

Note that, if you reserve enough room in the std::vector with the reserve() method, you get exactly 10 copy constructor calls:

vector<Person> persons;

// Reserve room in the vector to store 10 persons
persons.reserve(10);

for (int i = 0; i < 10; ++i)
{
    Person person(to_string(i + 1), i);
    persons.push_back(person);
}

That's because, in this case, you made enough room in the vector, so the vector's size doesn't exceed its capacity (so, there's no need to allocate a new larger buffer, and copying the old data to this new buffer).

All that being said, if your Person class is move-constructible, std::vector will move the previously created Person objects instead of copying them, which is faster.

If you add this line inside your Person class:

class Person 
{
  public:
   ...

   // Synthesize default move constructor
   Person(Person&&) = default;
   ...
};

you'll get exactly 10 copy constructor calls, even if you don't call the vector::reserve() method.

like image 45
Mr.C64 Avatar answered Nov 15 '22 07:11

Mr.C64