I have seen a few questions on std::vector recently, and out of curiosity I have been playing around with them a little. I've never really used the STL much, but I knew you could use vector to deal with the allocation of arrays of objects, and I could have sworn there was a way to use the default constructor to allocate the items within when the vector is created. Indeed, this question Initializing a std::vector with default constructor deals with initializing a vector using either a copy constructor and default value vs. just using the default constructor.
However, as I have been doing some experimenting in Visual Studio 2010 with a C++ Console Application project, I have not been getting results consistent with this explanation. According to one of the comments in the answer to the aforementioned question (given here), if you use, e.g., std::vector<FooClass> FooArray = new std::vector<FooClass>(20);
it should use the default constructor, and this was indeed the behavior I expected.
However I wrote some tracing code to track objects as they were created, assuming they would be created with the default constructor, and it appeared that every object was just created and subsequently immediately destroyed. Finally after much searching here, there, and everywhere, I went ahead and implemented a copy constructor that printed out information as well. What I am seeing is that if I initialize a vector of FooClass
using a default value, with, e.g., new std::vector<FooClass>(20, FooClass())
, then I get the expected result: a FooClass()
is instantiated, each of the items in the vector is initialized with the copy constructor as a copy of that object, and then the value used as the default is destroyed.
But, if I do new std::vector<FooClass>(20)
, instead of using the default constructor, it seems to be doing something that's a little (to me) bizarre. Twenty times, a temporary FooClass
object is created using the default constructor, an element of the array is constructed via the copy constructor using the temporary, and then the temporary is destroyed.
This really just doesn't make sense to me; but I wonder if perhaps I was just doing something wrong.
FooClass.h
#include <stdio.h>
class FooClass
{
public:
FooClass()
{
printf("Foo %i Created!\n", NumFoos);
myFooNumber = FooClass::NumFoos;
++FooClass::NumFoos;
myIsACopy = false;
}
FooClass(const FooClass& Another)
{
printf("Foo %i (a copy of Foo %i) Created!\n", FooClass::NumFoos, Another.myFooNumber);
myFooCopiedFrom = Another.myFooNumber;
myFooNumber = FooClass::NumFoos;
++FooClass::NumFoos;
myIsACopy = true;
}
void PrintMe()
{
if (myIsACopy)
printf("I'm Foo %i (a copy of Foo %i)!\n", myFooNumber, myFooCopiedFrom);
else
printf("I'm Foo %i!\n", myFooNumber);
}
~FooClass()
{
printf("Foo %i Deleted!\n", myFooNumber);
}
private:
int myFooCopiedFrom;
int myFooNumber;
bool myIsACopy;
private:
static int NumFoos;
};
FooClass.cpp
#include "FooClass.h"
int FooClass::NumFoos = 0;
FooVector.cpp
// FooVector.cpp : Defines the entry point for the console application.
#include "stdafx.h"
#include <memory>
#include <vector>
#include "FooClass.h"
//#define USE_INITIALIZER
int _tmain(int argc, _TCHAR* argv[])
{
#ifdef USE_INITIALIZER
std::vector<FooClass> myFooArray =
std::vector<FooClass>(5, FooClass());
#else
std::vector<FooClass> myFooArray =
std::vector<FooClass>(5);
#endif
for (int i=0; i < 5; ++i)
myFooArray[i].PrintMe();
printf("We're done!\n");
return 0;
}
Foo 0 Created! Foo 1 (a copy of Foo 0) Created! Foo 2 (a copy of Foo 0) Created! Foo 3 (a copy of Foo 0) Created! Foo 4 (a copy of Foo 0) Created! Foo 5 (a copy of Foo 0) Created! Foo 0 Deleted! I'm Foo 1 (a copy of Foo 0)! I'm Foo 2 (a copy of Foo 0)! I'm Foo 3 (a copy of Foo 0)! I'm Foo 4 (a copy of Foo 0)! I'm Foo 5 (a copy of Foo 0)! We're done!
Foo 0 Created! Foo 1 (a copy of Foo 0) Created! Foo 0 Deleted! Foo 2 Created! Foo 3 (a copy of Foo 2) Created! Foo 2 Deleted! Foo 4 Created! Foo 5 (a copy of Foo 4) Created! Foo 4 Deleted! Foo 6 Created! Foo 7 (a copy of Foo 6) Created! Foo 6 Deleted! Foo 8 Created! Foo 9 (a copy of Foo 8) Created! Foo 8 Deleted! I'm Foo 1 (a copy of Foo 0)! I'm Foo 3 (a copy of Foo 2)! I'm Foo 5 (a copy of Foo 4)! I'm Foo 7 (a copy of Foo 6)! I'm Foo 9 (a copy of Foo 8)! We're done!
So... Am I setting up my class improperly and this is the expected behavior? Is this perhaps a quirk of Microsoft's implementation of the STL?
Or is there some other explanation entirely?
I removed the sgi specs and comments thereon, because, as James's answer pointed out, the sgi specification is not the actual specification. See, for instance, the resources for wikipedia's entry on C++ for links to working drafts of the real specs. Thanks everyone! :)
This is a bug in the Visual C++ 2010 Standard Library implementation. This was also discussed in Standard library containers producing a lot of copies on rvalues in GCC.
The correct behavior depends on the version of the C++ specification your compiler and libraries are designed to.
In C++98/C++03 (what was the "current" C++ specification until last week), both versions of your code would call the same std::vector
constructor, which is declared as:
vector(size_type n, const T& x = T(), const Allocator& = Allocator());
The constructor makes n
copies of x
into the vector
. If you don't explicitly provide a T
object to be copied, one is constructed implicitly via the default argument. If you compile your code using Visual C++ 2008, you will find that your code has this behavior regardless whether you declare USE_INITIALIZER
. In both cases, you'll get the "Output with default initializer" result that you show.
In C++11 (current as of last week), the behavior is changed. This constructor has been split into two distinct constructors:
vector(size_type n, const T& x, const Allocator& = Allocator()); // (1)
vector(size_type n); // (2)
(1) is used if you explicitly provide an object to be copied and n
copies of x
are made into the vector
. (2) is used if you do not provide an object to be copied: n
objects of type T
are value initialized / default constructed in the vector
. No copies at all are made.
So, with a C++11 implementation, if you declare USE_INITIALIZER
, you'll get the same behavior as in C++03. If you do not declare USE_INITIALIZER
, you should get the following output.
Foo 0 Created!
Foo 1 Created!
Foo 2 Created!
Foo 3 Created!
Foo 4 Created!
I'm Foo 0!
I'm Foo 1!
I'm Foo 2!
I'm Foo 3!
I'm Foo 4!
We're done!
Visual C++ 2010 does not correctly implement the C++11 std::vector
constructors and it ends up creating and destroying a bunch of objects that it shouldn't. This should be fixed in a future version of Visual C++.
The standard allocator provides a construct
method that is used (internally by std::vector
) to construct objects on it's buffer. As far as I understand it there is no default constructor version, only a copy constructor version:
// construct a new object at address _ptr, by copying from _obj
allocator::construct(pointer _ptr, const_ref _obj)
In the second case, where you have not provided an object to the vector's constructor to copy from, a new temporary must be instantiated each time construct
is called, something like:
// obviously simplified, but to construct the ith object in the vector
allocator::construct(&vector_buffer[i], FooClass());
I think the standard allows extra constructors/destructors to be called, so there's nothing wrong going on, but you could definitely argue that it's sub-optimal.
Hope this helps.
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