I am a bit confused with the way vector push_back behaves, with the following snippet I expected the copy constructor to be invoked only twice, but the output suggest otherwise. Is it a vector internal restructuring that results in this behaviour.
Output:
Inside default
Inside copy with my_int = 0
Inside copy with my_int = 0
Inside copy with my_int = 1
class Myint
{
private:
int my_int;
public:
Myint() : my_int(0)
{
cout << "Inside default " << endl;
}
Myint(const Myint& x) : my_int(x.my_int)
{
cout << "Inside copy with my_int = " << x.my_int << endl;
}
void set(const int &x)
{
my_int = x;
}
}
vector<Myint> myints;
Myint x;
myints.push_back(x);
x.set(1);
myints.push_back(x);
What happens:
x is inserted via push_back. One copy occurs: The newly created element is initialized with the argument. my_int is taken over as zero because xs default constructor initialized it so.
The second element is push_back'd; The vector needs to reallocate the memory since the internal capacity was reached. As no move constructor is implicitly defined for Myint1 the copy constructor is chosen; The first element is copied into the newly allocated memory (its my_int is still zero... so the copy constructor shows my_int as 0 again) and then x is copied to initialize the second element (as with the first in step 1.). This time x has my_int set to one and that's what the output of the copy constructor tells us.
So the total amount of calls is three. This might vary from one implementation to another as the initial capacity might be different. However, two calls are be the minimum.
You can reduce the amount of copies by, in advance, reserving more memory - i.e. higher the vectors capacity so the reallocation becomes unnecessary:
myints.reserve(2); // Now two elements can be inserted without reallocation.
Furthermore you can elide the copies when inserting as follows:
myints.emplace_back(0);
This "emplaces" a new element - emplace_back is a variadic template and can therefore take an arbitrary amount of arguments which it then forwards - without copies or moves - to the elements constructor.
1 Because there is a user-declared copy constructor.
You got it...it was the resizing. But I'll just point out that if you're doing some bean counting on your constructors, you might be interested in "emplacement":
#include <iostream>
#include <vector>
using namespace std;
class Myint
{
private:
int my_int;
public:
explicit Myint(int value = 0) : my_int(value)
{
cout << "Inside default " << endl;
}
Myint(const Myint& x) : my_int(x.my_int)
{
cout << "Inside copy with my_int = " << x.my_int << endl;
}
Myint(const Myint&& x) noexcept : my_int(x.my_int) {
cout << "Inside move with my_int = " << x.my_int << endl;
}
};
int main() {
vector<Myint> myints;
myints.reserve(2);
myints.emplace_back(0);
myints.emplace_back(1);
// your code goes here
return 0;
}
That should give you:
Inside default
Inside default
And, due to the noexcept on the move constructor...if you delete the reserve you'd get a move, not a copy:
Inside default
Inside default
Inside move with my_int = 0
There's no real advantage with this datatype of a move over a copy. But semantically it could be a big difference if your data type was more "heavy weight" and had a way of "moving" its members that was more like transferring ownership of some pointer to a large data structure.
When the size of the vector is increased with the second push_back, the existing contents of the vector must be copied to a new buffer. To verify, output myints.capacity() after the first push_back, it should be 1.
This depends on how much memory was reserved to an object of type std::vector. It seems that when push_back was first executed there was allocated memory only for one element. When the second time push_back was called the memory was reallocated to reserve memory for the second element. In this case the element that is already in the vector is copied in the new place. And then the second element is also added.
You could reserve enough memory yourself that to escape the second call of the copy constructor:
vector<Myint> myints;
myints.reserve( 2 );
You're correct in assuming the additional invocation of the copy constructor comes from internal restructuring of the vector.
See this answer for more detail: https://stackoverflow.com/a/10368636/3708904
Or this answer for the reason why copy construction is necessary: https://stackoverflow.com/a/11166959/3708904
Related
I wrote the following code to understand the move sementics of std::vector
class PointerHolder
{
public:
PointerHolder()
{
cout << "Constructor called" << endl;
}
//copy constructor
PointerHolder(const PointerHolder& rhs)
{
cout << "Copy Constructor called" << endl;
}
//copy assignment operator
PointerHolder& operator = (const PointerHolder& rhs)
{
cout << "Copy Assignment Operator called" << endl;
return *this;
}
// move constructor
PointerHolder(PointerHolder&& rhs)
{
cout << "Move Constructor called" << endl;
}
// move assignment operator
PointerHolder& operator = (PointerHolder&& rhs)
{
cout << "Move Assignment Operator called" << endl;
return *this;
}
};
void processVector(std::vector<PointerHolder> vec)
{
}
int main()
{
vector<PointerHolder> vec;
PointerHolder p1;
PointerHolder p2;
vec.push_back(p1);
vec.push_back(p2);
cout << "Calling processVector\n\n" << endl;
processVector(std::move(vec));
}
Since I pass an Rvalue reference of the vector When calling processVecor, what Actually should get called is the move constructor of the std::vector when the function parameter object is formed. Is that right ?
So I expected the move constructor of the vecor within itself woud call the move constructor of the PointerHolder class.
But there was no evidence printed to confirm that.
Can you please clarify the behaviour. Doesn't the move constructor of the std::vector in turn call the move constructor of the individual items
No. Note that the complexity requirement of move contructor of std::vector is constant.
Complexity
6) Constant.
That means the move contructor of std::vector won't perform move operation on every individual elements, which will make the complexity to be linear (like copy constructor). The implementation could move the inner storage directly to achieve it.
No. It pilfers the entire block of memory with all the elements instead. Why bother moving the contents when you can just grab the whole thing?
(The allocator-extended move constructor will need to perform a memberwise move if the supplied allocator compares unequal to the source vector's allocator.)
No, it doesn't require the elements to be moved when calling the move constructor of the std::vector. To understand why, I think you should have a good mental model of how std::vector is implemented. (Most implementations look like this, except they need some more complexity for dealing with allocators)
So what is std::vector?
In the simplest form, it has 3 members:
A capacity: (size_t)
A size: (size_t)
A pointer to data (T*, std_unique_ptr, void*)
The size indicates how many elements in use, the capacity indicates how much data fits in the currently allocated data. Only when your new size would become larger than the capacity, the data needs to be reallocated.
The data that is allocated is uninitialized memory, in which in-place the elements get constructed.
So, given this, implementing the move of a vector would be:
Copy over capacity/size
Copy over the pointer and set to nullptr in original (same behavior as unique_ptr)
With this, the new instance is completely valid. The old one is in a valid but unspecified state. This last one means: you can call the destructor without crashing the program.
For vector, you can also call clear to bring it back to a valid state, or the operator=.
Given this model, you can easily explain all operators. Only move-assignment is a bit more complex.
No. It doesn't call the move constructor. To call move constructor of element you will have to call std::move while pushing to vector itself.
int main()
{
vector<PointerHolder> vec;
PointerHolder p1;
PointerHolder p2;
vec.push_back(std::move(p1));
vec.push_back(p2);
cout << "Calling processVector\n\n" << endl;
processVector(std::move(vec));
}
output
Constructor called
Constructor called
Move Constructor called
Copy Constructor called
Calling processVector
If we're looking at the standard (https://en.cppreference.com/w/cpp/container/vector/vector), it says, moving requires constant amount of time O(1).
Regardless of that, if we're looking at most common implementations, std::vector is a dynamic array. A dynamic array first allocates for example space for 8 elements. If we need space for 9 that this 8 is multiplied or increased by a specific amount, often multiplied bei 1.44, 2 or sth. like that.
But as concerns the moving aspect: what are our member variables and how do we move them? Well, the dynamic array is just - as mentioned - a pointer to the first element and if we wanna move the structure we'll copy the pointer to the other object and set the old pointer to nullptr (or NULL if you don't care for the issue for what nullptr has been implemented for, nullptr is obviously preferrable). And of course things like internal saved size (if saved) has to be copied and in the old object has to be set to zero as well (or whatever move semantics are there).
I am a bit confused with the way vector push_back behaves, with the following snippet I expected the copy constructor to be invoked only twice, but the output suggest otherwise. Is it a vector internal restructuring that results in this behaviour.
Output:
Inside default
Inside copy with my_int = 0
Inside copy with my_int = 0
Inside copy with my_int = 1
class Myint
{
private:
int my_int;
public:
Myint() : my_int(0)
{
cout << "Inside default " << endl;
}
Myint(const Myint& x) : my_int(x.my_int)
{
cout << "Inside copy with my_int = " << x.my_int << endl;
}
void set(const int &x)
{
my_int = x;
}
}
vector<Myint> myints;
Myint x;
myints.push_back(x);
x.set(1);
myints.push_back(x);
What happens:
x is inserted via push_back. One copy occurs: The newly created element is initialized with the argument. my_int is taken over as zero because xs default constructor initialized it so.
The second element is push_back'd; The vector needs to reallocate the memory since the internal capacity was reached. As no move constructor is implicitly defined for Myint1 the copy constructor is chosen; The first element is copied into the newly allocated memory (its my_int is still zero... so the copy constructor shows my_int as 0 again) and then x is copied to initialize the second element (as with the first in step 1.). This time x has my_int set to one and that's what the output of the copy constructor tells us.
So the total amount of calls is three. This might vary from one implementation to another as the initial capacity might be different. However, two calls are be the minimum.
You can reduce the amount of copies by, in advance, reserving more memory - i.e. higher the vectors capacity so the reallocation becomes unnecessary:
myints.reserve(2); // Now two elements can be inserted without reallocation.
Furthermore you can elide the copies when inserting as follows:
myints.emplace_back(0);
This "emplaces" a new element - emplace_back is a variadic template and can therefore take an arbitrary amount of arguments which it then forwards - without copies or moves - to the elements constructor.
1 Because there is a user-declared copy constructor.
You got it...it was the resizing. But I'll just point out that if you're doing some bean counting on your constructors, you might be interested in "emplacement":
#include <iostream>
#include <vector>
using namespace std;
class Myint
{
private:
int my_int;
public:
explicit Myint(int value = 0) : my_int(value)
{
cout << "Inside default " << endl;
}
Myint(const Myint& x) : my_int(x.my_int)
{
cout << "Inside copy with my_int = " << x.my_int << endl;
}
Myint(const Myint&& x) noexcept : my_int(x.my_int) {
cout << "Inside move with my_int = " << x.my_int << endl;
}
};
int main() {
vector<Myint> myints;
myints.reserve(2);
myints.emplace_back(0);
myints.emplace_back(1);
// your code goes here
return 0;
}
That should give you:
Inside default
Inside default
And, due to the noexcept on the move constructor...if you delete the reserve you'd get a move, not a copy:
Inside default
Inside default
Inside move with my_int = 0
There's no real advantage with this datatype of a move over a copy. But semantically it could be a big difference if your data type was more "heavy weight" and had a way of "moving" its members that was more like transferring ownership of some pointer to a large data structure.
When the size of the vector is increased with the second push_back, the existing contents of the vector must be copied to a new buffer. To verify, output myints.capacity() after the first push_back, it should be 1.
This depends on how much memory was reserved to an object of type std::vector. It seems that when push_back was first executed there was allocated memory only for one element. When the second time push_back was called the memory was reallocated to reserve memory for the second element. In this case the element that is already in the vector is copied in the new place. And then the second element is also added.
You could reserve enough memory yourself that to escape the second call of the copy constructor:
vector<Myint> myints;
myints.reserve( 2 );
You're correct in assuming the additional invocation of the copy constructor comes from internal restructuring of the vector.
See this answer for more detail: https://stackoverflow.com/a/10368636/3708904
Or this answer for the reason why copy construction is necessary: https://stackoverflow.com/a/11166959/3708904
Assuming I have a std::vector V of 5 elements,
V.erase(V.begin() + 2) remove the 3rd element.
STL vector implementation will move 4th and 5th element up, and then destruct the 5th element.
I.e. erasing element i in a vector does not guarantee that ith destructor is called.
For std::list, this is not the case. Erasing ith element invokes ith element's destructor.
What does STL say about this behavior?
This is code taken from my system's stl_vector.h:
392 iterator erase(iterator __position) {
393 if (__position + 1 != end())
394 copy(__position + 1, _M_finish, __position);
395 --_M_finish;
396 destroy(_M_finish);
397 return __position;
The C++11 standard 23.3.6.5/4 says (emphasis is mine):
Complexity: The destructor of T is called the number of times equal to the number of the elements erased, but the move assignment operator of T is called the number of times equal to the number of elements in the vector after the erased elements.
Had the implementation called the destructor on the 3rd element, it wouldn't be conform.
Indeed, suppose that the destructor is called on the 3rd element. Since only one element is erased, the desctructor cannot be called again.
After the destructor call, the 3rd position contains raw memory (not a fully constructd object T). Hence the implementation needs to call the move constructor to move from the 4th position to the 3rd one.
It cannot destroy the 4th element (because it can no longer call the destructor) and then to move from the 5th to the 4th element it must call the move assignment operator.
At this point, the implementation still needs to decrease the vector size by 1 and destroy the 5th element but, as we have seen, no other destrucor call is allowed. (Notice also that the move assignement operator would not be called twice as required by the standard.) QED.
The standard says that's expected, the specification for vector::erase(const_iterator) (in the table of Sequence container requirements) says that the requirements on that function are:
For vector and deque, T shall be MoveAssignable.
The reason for requiring MoveAssignable is that each of the following elements will be (move) assigned over the element before them, and the last element destroyed.
In theory it would have been possible for the original STL to have done it differently and to have destroyed the erased element as you expect, but there are good reasons that wasn't chosen. If you only destroy the erased element you leave a "hole" in the vector, which isn't an option (the vector would have to remember where holes were and if a user says v[5] the vector would have to remember there's a hole there and return v[6] instead.) So it's necessary to "shuffle" the later elements down to fill the hole. That could have been done by destroying the Nth element in place (i.e. v[N].~value_type()) and then using placement new to create a new object at that location (i.e. ::new ((void*)&v[N]) value_type(std::move(v[N+1]))) and then doing the same for each following element, until you get to the end, however that would result in far worse performance in many cases. If the existing elements have allocated memory, e.g. are containers themselves, then assigning to them may allow them to reuse that memory, but destroying them and then constructing new elements would require deallocating and reallocating memory, which may be much slower and could fragment the heap. So there is a very good reason to us assignment to alter the elements' values, without necessarily altering their identities.
This isn't the case for std::list and other containers because they do not store elements in a contiguous block like vector and deque, so removing a single element just involves adjusting the links between the neighbouring elements, and there is no need to "shuffle" other elements down the block to take up the empty position.
This is perfectly valid behaviour. #Cassio Neri pointed out why it is required by the standard.
Short:
"std::vector::erase(iterator position) does not necessarily invoke the corresponding element's destructor" [Op; Headline] but a destructor is invoked, handling the data of the corresponding elements which has been transfered to another object (either via move constructor to the moved-from or via RAII to the temporary instance).
Long:
Why you don't have to rely on the ith destructor to be called.
I'll provide some hints why you shouldn't worry at all, which destructor is called in this case.
Consider the following small class
class test
{
int * p;
public:
test (void) : p(new int[5]) { cout << "Memory " << p << " claimed." << endl; }
~test (void) { cout << "Memory " << p << " will be deleted." << endl; delete p; }
};
If you handle your object move-assignment correctly there is no need to worry about the fact which destructor is called properly.
test& operator= (test && rhs)
{
cout << "Move assignment from " << rhs.p << endl;
std::swap(p, rhs.p);
return *this;
}
Your move assignment operator has to transfer the state of the object that is "overwritten" into the object that is "moved from" (rhs here) so it's destructor will take proper action (if there is something the destructor needs to take care of). Perhaps you should use something like a "swap" member function to do the transfer for you.
If your object is non-moveable you'll have to handle the "cleanup" (or whatever action that relies on the current state of the object) of the erased object in the copy assignment operation before you copy the new data into the object.
test& operator= (test const &rhs)
{
test tmp(rhs);
std::swap(p, tmp.p);
return *this;
}
Here we use RAII and again the swap (which may still be a member function, too; but test only has one pointer...). The destructor of tmp will make things cosy.
Let's do a small test:
#include <vector>
#include <iostream>
using namespace std;
class test
{
int * p;
public:
test (void) : p(new int[5]) { cout << "Memory " << p << " claimed." << endl; }
test& operator= (test && rhs)
{
cout << "Move assignment from " << rhs.p << endl;
std::swap(p, rhs.p);
return *this;
}
~test (void) { cout << "Memory " << p << " will be deleted." << endl; delete p; }
};
int main (void)
{
cout << "Construct" << endl;
std::vector<test> v(5);
cout << "Erase" << endl;
v.erase(v.begin()+2);
cout << "Kick-off" << endl;
return 0;
}
Results in
Construct
Memory 012C9F18 claimed.
Memory 012CA0F0 claimed.
Memory 012CA2B0 claimed. // 2nd element
Memory 012CA2F0 claimed.
Memory 012CA110 claimed.
Erase
Move assignment from 012CA2F0
Move assignment from 012CA110
Memory 012CA2B0 will be deleted. // destruction of the data of 2nd element
Kick-off
Memory 012C9F18 will be deleted.
Memory 012CA0F0 will be deleted.
Memory 012CA2F0 will be deleted.
Memory 012CA110 will be deleted.
Every memory location that is claimed will be released properly if your move (or copy) assignment operation hands over the critical properties to the object that will be destroyed.
Every destructor that relies on the internal state of an object will be called with the proper object around if your assignment operations are designed properly.
Unlike std::list, std::vector holds its elements contiguously. So when an element is erased from the middle of the container, it would make more sense to copy assign all elements that need to be shifted. In this scenario, the destructor of the last shifted element would be called. This avoids a re-allocation of the whole data of the vector.
In reference to example by Mats Petersson, perhaps this example will show more clearly that destroy 2 really happens, we just don't have destructor available for built-in type where we can conveniently add the printout statement:
#include <vector>
#include <iostream>
#include <utility>
using namespace std;
struct Integer
{
int x;
Integer(int v) : x(v) {}
~Integer() { cout << "Destroy Integer=" << x << endl; }
};
class X
{
Integer Int;
public:
X(int v) : Int(v) {}
X operator=(const X& a)
{
auto tmp(a.Int);
swap(this->Int, tmp);
cout << "copy x=" << Int.x << endl;
return *this;
}
};
int main()
{
vector<X> v;
for(int i = 0; i < 5; i++)
{
X a(i);
v.push_back(a);
}
cout << "Erasing ... " << endl;
v.erase(v.begin() + 2);
}
This will print:
Destroy Integer=0
Destroy Integer=0
Destroy Integer=1
Destroy Integer=0
Destroy Integer=1
Destroy Integer=2
Destroy Integer=0
Destroy Integer=1
Destroy Integer=2
Destroy Integer=3
Destroy Integer=0
Destroy Integer=1
Destroy Integer=2
Destroy Integer=3
Destroy Integer=4
Erasing ...
Destroy Integer=2
copy x=3
Destroy Integer=2
Destroy Integer=3
Destroy Integer=3
copy x=4
Destroy Integer=3
Destroy Integer=4
Destroy Integer=4
(skipped printout of destructor calls for entire vector on program exit)
One way of looking at this is to ask yourself: what does it mean to erase an object from a vector? It means that, given a way to identify that object, you won't be able to find it in a vector after the erase. Maybe it was a value that got overwritten, thereby acquiring a new identity. If it held resources that could identify it, those will be properly released, as others mentioned, as long as move, assignment and copy do the right thing. Additionally, the size of vector would reflect that there is one object less.
For your philosophical amusement, here are some notes by Stepanov (primary STL author):
Integral parts of an object are those parts of the object needed to
realize its primary purpose. Connections among integral parts
constitute the integral form of the object. Two intuitive constraints
that we have on the definition of essential parts are (i) for certain
objects, it is possible to take them apart which would result i n
their losing their identity and later they could be brought together
which would imply their regaining their identity. This allows objects
to exist, disappear and later reappear; thus there is a discontinuity
in their existence. (ii) some essential parts of an object can be
replaced one by one without the object losing its identity. To define
identity across time, we introduce the notion of essential parts and
essential form.
Definition: An essential part of an object is an integral part such that if it is removed, the object loses its identity, hence it
disappears.
Here's a small program that shows the problem, and yes, if you RELY on the destructor being called for that very object, you need to do something other than what this code does:
#include <iostream>
#include <vector>
using namespace std;
class X
{
int x;
public:
X(int v) : x(v) {}
~X() { cout << "Destroy v=" << x << endl; }
X operator=(const X& a) { x = a.x; cout << "copy x=" << x << endl; return *this; }
};
int main()
{
vector<X> v;
for(int i = 0; i < 5; i++)
{
X a(i);
v.push_back(a);
}
cout << "Erasing ... " << endl;
v.erase(v.begin() + 2);
}
The output is:
Destroy v=0
Destroy v=0
Destroy v=1
Destroy v=0
Destroy v=1
Destroy v=2
Destroy v=3
Destroy v=0
Destroy v=1
Destroy v=2
Destroy v=3
Destroy v=4
Erasing ...
copy x=3
Destroy v=3
copy x=4
Destroy v=4 <<< We expedct "destroy 2", not "destroy 4".
Destroy v=4
Destroy v=0
Destroy v=1
Destroy v=3
Destroy v=4
One variant to solve this would be to store a (smart) pointer, and manually copy out the pointer and then delete it.
I have this below program, where I am passing a vector by reference to a function myFunc and inside this function, I am adding few elements to the vector.
I am not freeing object creating with new, for now ignore the memory leak due to this.
After myFunc() execution is complete I am printing the variables ctor and dtor
to know how many times constructor and destructor were called.
Output is:
Before Exiting 5 7
I am creating 5 objects so ctor is 5. But why is dtor 7? From where do the extra two counts? Am I missing something?
#include
#include
using namespace std;
static int ctor = 0;
static int dtor = 0;
class MyClass
{
public:
MyClass(int n)
{
i = n;
ctor++;
// cout << "Myclass ctor " << ctor << endl;
}
~MyClass()
{
dtor++;
// cout << "Myclass dtor" << dtor << endl;
}
private:
int i;
};
void myFunc(vector<MyClass> &m);
void myFunc(vector<MyClass> &m)
{
MyClass *mc;
for(int i = 0; i < 5; i++)
{
mc = new MyClass(i);
m.push_back(*mc);
}
}
int main()
{
vector<MyClass> m;
vector<MyClass>::iterator it;
myFunc(m);
cout << "Before Exiting " << ctor << " " << dtor << endl;
}
Vectors copy around objects, but only your int constructor increments ctor. That's not accounting for copy constructed objects, and because you didn't provide one, the compiler provided it for you.
Add
MyClass(const MyClass& rhs) i(rhs.i) { ++ctor; }
to your class to see if that balances the count.
Vectors start at a low size. When you push an element into them, they make a copy of it using the copy constructor so you don't see your normal constructor get called. When the vector size grows beyond its limit, it will increase its limit by a multiple of its current size (e.g. double it).
Vectors are guaranteed to always keep objects in contiguous memory, so if adding a new object exceeds the vectors capacity() (i.e. size() + 1 > capacity()), the vector allocates new memory somewhere and copies all elements into it. This will again use the copy constructor. So, your elements from the vector pre-resize will call their destructors after they are copied into the newly allocated space with their copy-constructor.
So, more destructor calls than normal constructor calls :)
Vectors sometimes call another constructor, copy constructor, which was implicitly generated by the compiler for your class. That's why some ctor++ calls are missing: not all objects were constructed with the constructor you defined, some were constructed with the other one.
To ensure correct behavior of a type (class) with vector, you must implement copy constructor for it:
MyClass(const MyClass& rhs) { i = rhs.i; ++ctor; } // copy constructor
...because the one generated by compiler does nothing.
As indicated by others, the reason behind your result is the copy constructor and resizing of the vector. A vector has both a size and a capacity. The capacity is normally always doubled when the vector has to resize to accommodate new elements so that resizing does not have to happen all that often.
Adding some trace code to print out the vector capacity between each push_back gives more clarity into this behaviour.
m.capacity(): 0
m.capacity(): 1
m.capacity(): 2
m.capacity(): 4
m.capacity(): 4
m.capacity(): 8
Before Exiting 5 7
What actually happens here is that the only time the destructor is called is when the vector is resized (see why below). The first time it's resized it has no elements, thus the destructor is never called. The second time, the capacity is 1 so the destructor is called once. The third time it's called twice and the fourth time it's called four times. This totals seven times called, just as the counter shows.
The elements dynamically allocated in myFunc are never deallocated, so the destructor never runs there, and the final printout ("Before Exiting...") is done before leaving the scope in which the vector is allocated, so the destructor for the last "vector reincarnation" isn't called until after that printout. Therefore, the destructor of MyClass is only called when the vector is resized.
ยง 12.8.8 of the C++ standard says: If the class definition does not explicitly declare a copy constructor, there is no user-declared move constructor, and there is no user-declared move assignment operator, a copy constructor is implicitly declared as defaulted (8.4.2). Such an implicit declaration is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor.
Basically, since your struct violates the rule of five, the compiler made a copy constructor and assignment operator for you. That other constructor doesn't increment ctor, but uses the destructor you defined. The vector is then using that alternate constructor, as a speed improvement.
If you add protected: MyClass(const MyClass& b); to the class declaration, this problem goes away.
Using g++, I observe that creating a vector of size zero calls the vector's parameterized object type's constructor once. It then is deleted. Why does this happen?
#include <iostream>
#include <vector>
using namespace std;
class s
{
public:
s() { cout << endl << "default s constructor" << endl; }
~s() { cout << endl << "default s destructor" << endl; }
};
int main()
{
vector<s> v(0);
}
Output:
default s constructor
default s destructor
Because you're explicitly passing an initial size, which calls a constructor that has another parameter whose default value is s(). Just leave out the (0) (i.e. std::vector<s> v;) and it won't happen.
For completeness, the Standard 23.2.4-2 defines the constructor you're calling as:
explicit vector(size_type n, const T& value =T(),
const Allocator& = Allocator());
Aside (relevant to C++03 but not C++11)
Another interesting behavioural aspect of this constructor also raises its head on S.O. periodically: when the initial number of elements requested is > 0, it copy-constructs those elements from the prototypal parameter to the constructor:
people often put a default constructor that leaves member variables uninitialised, hoping to make vector(n) almost as fast as the underlying free store allocation, BUT
the copy-constructor is still called n times to copy the "garbage" content of the prototypal object into each of the requested elements
This has an obvious performance cost, but can also crash the application if the garbage content includes e.g. pointers that the copy-constructor can only assume are valid. Similarly, it's extremely dangerous to even push_back such an uninitialised garbage object - it lacks proper value semantic encapsulation and may be copied as the vector resizes, algorithmic operations like std::sort() are performed on the vector etc..
The actual constructor you are calling is (from cplusplus.com):
explicit vector ( size_type n, const T& value= T(), const Allocator& = Allocator() );
So even though you only specify the size, a new T object is created for second parameter, and will therefore also be destroyed at the conclusion of the constructor.