I have a C++03 application where std::vector<T>
types are used throughout as temporary buffers. As such, they often get resized using std::vector<T>::resize()
to ensure they are large enough to hold the required data before use. The C++03 prototype for this function is actually:
void resize(size_type n, value_type val = value_type());
So in actuality when calling resize()
, the vector is enlarged by adding the appropriate number of copies of val
. Often, however, I just need to know that the vector
is large enough to hold the data I need; I don't need it initialized with any value. Copy-constructing the new values is just a waste of time.
C++11 comes to the rescue (I thought): in its specification, it splits resize()
into two overloads:
void resize(size_type n); // value initialization
void resize(size_type n, const value_type &val); // initialization via copy
This fits nicely with the philosophy of C++: only pay for what you want. As I noted, though, my application can't use C++11, so I was happy when I came across the Boost.Container library, which indicates support for this functionality in its documentation. Specifically, boost::container::vector<T>
actually has three overloads of resize()
:
void resize(size_type n); // value initialization
void resize(size_type n, default_init_t); // default initialization
void resize(size_type n, const value_type &val); // initialization via copy
In order to verify that I understood everything, I whipped up a quick test to verify the behavior of C++11 std::vector<T>
and boost::container::vector<T>
:
#include <boost/container/vector.hpp>
#include <iostream>
#include <vector>
using namespace std;
namespace bc = boost::container;
template <typename VecType>
void init_vec(VecType &v)
{
// fill v with values [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for (size_t i = 0; i < 10; ++i) v.push_back(i);
// chop off the end of v, which now should be [1, 2, 3, 4, 5], but the other 5 values
// should remain in memory
v.resize(5);
}
template <typename VecType>
void print_vec(const char *label, VecType &v)
{
cout << label << ": ";
for (size_t i = 0; i < v.size(); ++i)
{
cout << v[i] << ' ';
}
cout << endl;
}
int main()
{
// instantiate a vector of each type that we're going to test
std::vector<int> std_vec;
bc::vector<int> boost_vec;
bc::vector<int> boost_vec_default;
// fill each vector in the same way
init_vec(std_vec);
init_vec(boost_vec);
init_vec(boost_vec_default);
// now resize each vector to 10 elements in ways that *should* avoid reinitializing the new elements
std_vec.resize(10);
boost_vec.resize(10);
boost_vec_default.resize(10, bc::default_init);
// print each one out
print_vec("std", std_vec);
print_vec("boost", boost_vec);
print_vec("boost w/default", boost_vec_default);
}
Compiling this with g++
4.8.1 in C++03 mode as follows:
g++ vectest.cc
./a.out
yields the following output:
std: 0 1 2 3 4 0 0 0 0 0
boost: 0 1 2 3 4 0 0 0 0 0
boost w/default: 0 1 2 3 4 5 6 7 8 9
This isn't too surprising. I expect the C++03 std::vector<T>
to initialize the final 5 elements with zeros. I can even convince myself why boost::container::vector<T>
is doing the same (I would assume it emulates C++03 behavior in C++03 mode). I only got the effect that I wanted when I specifically ask for default initialization. However, when I rebuilt in C++11 mode as follows:
g++ vectest.cc -std=c++11
./a.out
I get these results:
std: 0 1 2 3 4 0 0 0 0 0
boost: 0 1 2 3 4 0 0 0 0 0
boost w/default: 0 1 2 3 4 5 6 7 8 9
Exactly the same! Which leads to my question:
Am I wrong in thinking that I should see the same results from each of the three tests in this case? This seems to indicate that the std::vector<T>
interface change hasn't really had any effect, as the 5 elements added in the final call to resize()
still get initialized with zeros in the first two cases.
Value initialization of
int
yields 0.Default initialization of
int
doesn't initialize the value at all - it just retains whatever was in memory.Either the memory allocated by
resize(10)
wasn't released byresize(5)
, or the same memory block was reused. Either way you ended up with the prior contents left over.There is a small functional difference with the C++11
resize
signatures, but your test will not expose it. Consider this similar test:Under C++03 this prints:
But under C++11 it prints:
The motivation for this change is to better support non-copyable (move-only) types in
vector
. Most of the time, including in your case, this change makes no difference.There is a way to accomplish what you want in C++11 with the use of a custom allocator (which your compiler may or may not yet support):
Which should output:
The
no_init_alloc
simply refuses to do any initialization, which is fine forint
, leaving it with an unspecified value. I had to change yourinit_vec
to use assignment to initialize instead of using construction though. So this can be dangerous / confusing if you are not careful. However it does avoid doing unnecessary initialization.Not an answer, but a lengthy addendum to Howard's: I use an allocator adapter that basically works the same as Howard's allocator, but is safer since
No, not really. Having a container of elements that are not actually constructed does not make sense. I'm not sure what you expected to see other than zeroes. Unspecified/uninitialised elements? That's not what value-initialisation means.
If you need N elements, then you should have N properly-constructed elements, and that is what
std::vector::resize
does. Value-initialisation will zero-initialise an object with no default constructor to invoke, so really it's the opposite of what you seem to want, which is less safety and initialisation rather than more.I suggest that what you're really after is
std::vector::reserve
.It certainly has an effect, just not the one you're looking for. The new
resize
overload is for convenience so that you don't have to construct your own temporary when default- or even value-initialisation is all you need. It isn't a fundamental change to how containers work, which is that they always hold valid† instances of types.† Valid but in an unspecified state if you move from them!
Uninitialized values
You may have initialized value by creating the appropriate class. As the following:
The output is identical to "boost w/default".
Or create a custom allocator with
construct
anddestroy
as nop.Splitting
resize
prototypeIf
void std::vector<T>::resize(size_type n)
does whatvoid bc::vector<T>::resize(size_type n, default_init_t)
does, then lot of old valid code would break...The splits of
resize()
allows to resize vector of 'move only' classes as the following:if you want to use a vector with the standard allocator, doesn't this work in C++11??