19

In C++ there are few compelling reasons to use a C array over std::vector. One of those few compelling reasons, at least with C++03, was the fact that it is impossible to use a vector to allocate an uninitialized array of objects. The "fill" constructor for std::vector is:

vector(size_type count, const T& value = T())

Meaning that...

int* array = new array[1000000];

is likely to be much more efficient than:

std::vector<int> v(1000000);

...since the vector constructor will have to zero-initialize the array of integers. Thus, when working with a vector of PODs, there is no real equivalent to malloc; the best you can get is an equivalent to calloc.

C++11 seems to have changed this, with the concept of "value-initialization." In C++11, std::vector has a new constructor which takes a single size_type value, with no default argument. This "value-initializes" all elements in the vector. The C++11 standard distinguishes between "value-initialization" and "zero-initialization."

My understanding is that "value-initialization" is equivalent to calling the default constructor on T. If T is a POD type like int, then the default constructor simply creates an uninitialized integer. Thus, in C++11, explicit vector::vector(size_type count) is truly equivalent to malloc if T is a POD.

However, my understanding of this is based on the draft C++11 standard, rather than the final standard.

Question: Is my understanding correct here? Does explicit vector::vector(size_type count) provide an uninitialized array (similar to malloc) if T is a POD?

Channel72
  • 24,139
  • 32
  • 108
  • 180
  • 7
    Value initialization means zero initialization for built-in types. – juanchopanza Feb 26 '13 at 19:40
  • 5
    If you want uninitialized storage then use `vector::reserve`, as always. – Pubby Feb 26 '13 at 19:41
  • 1
    @Channel: C++03 also had value initialization as opposed to default initialization and zero initialization. The only relevant bit that changed was `std::vector` itself. – Mooing Duck Feb 26 '13 at 19:42
  • @Pubby: That's undefined behavior IIRC, due to copies not working as expected. – Mooing Duck Feb 26 '13 at 19:44
  • @MooingDuck: If you actually want to use those integers, then you obviously need to `push_back` them. It'd be UB for the `malloc`'d array to be copied without initializing those values too. Granted, it's not as straight-forward as simply assigning all the places, but what matters here is the safety brought to you by the abstraction. – Xeo Feb 26 '13 at 19:46
  • 3
    What's wrong with `reserve()` and then `push_back()`? – ipc Feb 26 '13 at 19:46
  • I don't know how well it would work, but if you _really_ need it, make a vector of `struct ui{int value;ui(){}explicit ui(int i):value(i){}int& operator(){return value;}const int& operator()const{return value;}};` which (once optimized) the compiler will treat as an `int` that won't be value initialized. – Mooing Duck Feb 26 '13 at 19:46
  • @MooingDuck: This class already exists and it's called `aligned_storage` (well, a typedef with appropriate template parameters to `uninit_int` may be convenient). – ipc Feb 26 '13 at 19:48
  • @ipc: While I believe it most like does, I can't find it. Closest I saw was [get_temporary_buffer](http://en.cppreference.com/w/cpp/memory/get_temporary_buffer) – Mooing Duck Feb 26 '13 at 19:49
  • Value-initialization existed in C++03, did it not? – GManNickG Feb 26 '13 at 19:49
  • 1
    @MooingDuck: Sorry, [`aligned_storage`](http://en.cppreference.com/w/cpp/types/aligned_storage). – ipc Feb 26 '13 at 19:50
  • @ipc: While that's probably not a bad idea, mine enforces the `int` type, and simplifies usage a hair. – Mooing Duck Feb 26 '13 at 19:52
  • @MooingDuck: True, a generic `force_uninitialized` wouldn't be a bad idea. – ipc Feb 26 '13 at 19:53

2 Answers2

24

Question: Is my understanding correct here? Does explicit vector::vector(size_type count) provide an uninitialized array (similar to malloc) if T is a POD?

No. There is a difference here between C++03 and C++11, but that isn't it. The difference is that in C++03, vector<T>(N) would default construct a T, and then make N copies of it to populate the vector.

Whereas in C++11, vector<T>(N) will populate the vector by default constructing T N times. For POD types the effect is identical. Indeed, I would expect that for almost all types the effect is identical. However for something like a unique_ptr (a move-only type), the difference is critical. The C++03 semantics would never work since you can not make a copy of a move-only type.

So:

vector<unique_ptr<int>> v(10);

creates a vector of 10 null unique_ptrs (which are not copies of each other).

In the rare case that it makes a difference and you need the C++03 behavior that can easily be accomplished with:

vector<T> v(10, T());
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 5
    The other potential difference is if it's a vector of a type that acts like a shared resource (so that copies all reference the same underlying resource). If that's default constructible, C++03 would make N copies, all referencing the same underlying resource, while C++11 would create N unrelated objects. – Dave S Feb 26 '13 at 20:13
  • @DaveS: Right, thanks. I've updated the answer with instructions on how to recover this behavior. – Howard Hinnant Feb 27 '13 at 00:45
9

Note: the value-initialization happens in the allocator, so if you want a vector to do default initialization instead of value initialization for default constructed elements, you can do something like:

template<typename T>
struct DefaultInitAllocator {
    template<typename U>
    void construct(U* p)
    { ::new (static_cast<void*>(p)) U; }

    template<typename U, typename... Args>
    void construct(U* p, Args&&... args)
    { ::new (static_cast<void*>(p)) U(std::forward<Args>(args)...); }

    // ... rest of the allocator interface
};

// ...
typedef std::vector<int, DefaultInitAllocator<int>> DefaultInitVectorInt;
Nevin
  • 4,595
  • 18
  • 24
  • seems like just using `reserve()` is less hassle. – jiggunjer Jul 12 '15 at 20:32
  • 2
    reserve() doesn't let you legally access the space. – Nevin Jul 13 '15 at 15:45
  • when you push_back after reserving, there are no allocations, right? – jiggunjer Jul 13 '15 at 16:31
  • Right, no allocations (assuming you reserved enough space, of course). But push_back is initializing the back element. Plus, I don't see what push_back() has to do with the OP question. If you want an array that you are, for example, about to Posix read() into it, you don't want to 0-fill it before doing so, which the default allocator will do. – Nevin Jul 13 '15 at 19:09
  • @jiggunjer - Push_back only solves the problem if you do not need to initialize elements in an arbitrary order. It also assumes one is free to change the code that uses the vector. – Jive Dadson Dec 08 '16 at 20:26
  • http://stackoverflow.com/questions/41049143/is-it-possible-stdvectordouble-my-vecsz-which-is-allocated-but-not-initi Therein resides a full, working example of an allocator that does as wanted. – Jive Dadson Dec 09 '16 at 13:10
  • The https://stackoverflow.com/a/21028912/427158 answer contains a small but complete allocator adaptor that safely replaces value initialization with default initialization and thus it **is** possible to create a std::vector with uninitialized array elements. – maxschlepzig Sep 25 '18 at 07:53