Reputation: 1942
While trying to experiment with copy construction by 'pass by value' and the followed destruction, I tried this code:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Rock{
int sz;
public:
Rock():sz(0){cout<< "Default ctor"<<endl;}
~Rock(){cout<< "Dtor"<<endl;}
Rock(const Rock& r){ cout << "Copy ctor" << endl; sz = r.sz;}
Rock& operator=(const Rock& r) {cout << "In assignment op" << endl; sz = r.sz;}
};
int main()
{
vector<Rock> rocks;
Rock a, b, c;
rocks.push_back(a);
rocks.push_back(b);
rocks.push_back(c);
return 0;
}
And got the below output. Up to line 7 everything is fine, however I could not understand what happens from then on. Can someone clarify?
Default ctor
Default ctor
Default ctor
Copy ctor
Copy ctor
Copy ctor // all fine I got it...
Dtor
Copy ctor
Copy ctor
Copy ctor
Dtor
Dtor
Dtor
Dtor
Dtor
Dtor
Dtor
Dtor
Upvotes: 1
Views: 136
Reputation: 19721
Let's associate the output to the corresponding code lines.
Rock a, b, c;
Default ctor
Default ctor
Default ctor
That one you probably figured out yourself. :-)
rocks.push_back(a);
Copy ctor
Again, you probably figured that out correctly.
rocks.push_back(b);
Copy ctor
Copy ctor // all fine I got it...
Dtor
Your comment is clearly wrong, because you almost certainly did not associate both constructor calls with this statement :-)
What happens is that when adding a copy of a
, the vector only allocated enough memory to store that one copy of a
(it would have been allowed to allocate more, though). Therefore it has to allocate a new memory block large enough to hold both copies of a
and b
, copy the copy of a
it stored in the old memory block over to the new one,copy b
after it, and then destroy the original copy of a
before deallocating the (now no longer needed) original memory block.
rocks.push_back(c);
Copy ctor
Copy ctor
Copy ctor
Dtor
Dtor
From the above explanation, you now should be able to guess what happens here.
Note however that if you pushed back yet another element to the vector, it's quite likely that you'd again get just one copy constructor and no destructor call. That's because a typical strategy for vectors is to double the allocated memory in each step, thus when pushing back c
it most probably allocated space for 4 objects. Actually std::vector
is required to use an exponential strategy (it is not required to use the factor 2, though, and it has been argued that the golden mean is a much better factor).
}
Dtor
Dtor
Dtor
Dtor
Dtor
Dtor
Here the three objects c
, b
, a
, and then the three objects in the vector are destructed.
Upvotes: 3
Reputation: 272467
Whenever a std::vector
has to grow its capacity (because you're pushing more elements in), it needs to allocate new storage, copy all the existing elements from the old storage to the new storage, and then delete the old elements. Hence, you'll get "extra" calls to the copy constructor and destructor.
If you put a cout
statement in-between each call to push_back
, it should help clarify which ctor/dtor calls are associated with each push_back
.
But this is my guess at the chronology:
Default ctor
Default ctor
Default ctor
// First push_back() (capacity initially 1)
Copy ctor // Copy a into vector
// Second push_back() (capacity now grows to 2)
Copy ctor // Copy rocks[0] to new storage
Copy ctor // Copy b into vector
Dtor // Destruct rocks[0] in old storage
// Third push_back() (capacity now grows to 4)
Copy ctor // Copy rocks[0] to new storage
Copy ctor // Copy rocks[1] to new storage
Copy ctor // Copy c into vector
Dtor // Destruct rocks[0] in old storage
Dtor // Destruct rocks[1] in old storage
// End of main
Dtor // Destruct rocks[0]
Dtor // Destruct rocks[1]
Dtor // Destruct rocks[2]
Dtor // Destruct c
Dtor // Destruct b
Dtor // Destruct a
Upvotes: 1
Reputation: 72469
It's easy to modify your code to print more information:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Rock{
int sz;
public:
Rock():sz(0){cout<< "Default ctor"<<endl;}
Rock(int x):sz(x){cout<< "int ctor " << x <<endl;}
~Rock(){cout<< "Dtor " << sz <<endl;}
Rock(const Rock& r){ cout << "Copy ctor from " << r.sz << endl; sz = r.sz;}
Rock& operator=(const Rock& r) {cout << "Copy ctor " << sz << " from " << r.sz << endl; sz = r.sz;}
};
int main()
{
vector<Rock> rocks;
Rock a(1), b(2), c(3);
rocks.push_back(a);
rocks.push_back(b);
rocks.push_back(c);
return 0;
}
It prints (with explanations):
int ctor 1
int ctor 2
int ctor 3
Copy ctor from 1 // copies a into the vector
// push_back(a) returns (capacity == 1)
Copy ctor from 1 // vector reallocates to a greater storage
Copy ctor from 2 // copies b into the vector
Dtor 1 // destroy the old elements
// push_back(b) returns (capacity == 2)
Copy ctor from 1 // vector reallocates to a greater storage
Copy ctor from 2
Copy ctor from 3 // copy c into the vector
Dtor 1 // destroy the old elements
Dtor 2
// push_back(c) returns (capacity == 4)
Dtor 3 // destroy the local a, b, c
Dtor 2
Dtor 1
Dtor 1 // destroy the vector and its elements
Dtor 2
Dtor 3
Upvotes: 2