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
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.
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.
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 .
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.
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
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With