Should allocator construct() default initialize instead of value initializing?
Asked Answered
S

1

19

As a followup to this question, the default allocator (std::allocator<T>) is required to implement construct as follows (according to [default.allocator]):

template <class U, class... Args>
void construct(U* p, Args&&... args);

Effects: ::new((void *)p) U(std::forward<Args>(args)...)

That is, always value-initialization. The result of this is that std::vector<POD> v(num), for any pod type, will value-initialize num elements - which is more expensive than default-initializing num elements.

Why didn't std::allocator provide a default-initializing additional overload? That is, something like (borrowed from Casey):

template <class U>
void construct(U* p) noexcept(std::is_nothrow_default_constructible<U>::value)
{
    ::new(static_cast<void*>(p)) U;
}

Was there a reason to prefer value initialization in call cases? It seems surprising to me that this breaks the usual C++ rules where we only pay for what we want to use.


I assume such a change is impossible going forward, given that currently std::vector<int> v(100) will give you 100 0s, but I'm wondering why that is the case... given that one could just as easily have required std::vector<int> v2(100, 0) in the same way that there are differences between new int[100] and new int[100]{}.

Sikh answered 9/3, 2016 at 21:26 Comment(4)
See P0040 adopted recently.Lashundalasker
@Lashundalasker Does that paper change what vector would do here? It just adds algorithms to the standard library right?Sikh
Right. So it is not an answer, just a comment. It provides convenient interface to ease the implementation of your workaround.Lashundalasker
Similar problem as make_unique<T[]> by the way.Woodbine
R
3

In C++03 Allocators construct member took two arguments: pointer and value which was used to perform copy-initialization:

20.1.6 Table 34

a.construct(p,t)

Effect:
    ::new((void*)p) T(t)

construct taking two parameters can be traced back to 1994 (pg. 18). As you can see, in orignal Stepanov concepts it wasn't part of allocator interface (it wasn't supposed to be configurable) and was present just as wrapper over placement new.

Only way to know for sure would to ask Stepanov himself, but I suppose that reason was following: if you want to construct something, you want to initialize it with specific value. And if you want your integers uninitializated, you can just omit construct call since it is not needed for POD types. Later construct and other related function were bundled into allocators and containers were parametrized on them introducing some loss of control on initialization for end user.

So it seems that lack of default initialization is for historical reasons: nobody though about its importance when C++ was standardized and later versions of the Standard would not introduce breaking change.

Regress answered 9/3, 2016 at 22:25 Comment(1)
I think this is the right interpretation. For good or for bad it is a design choice of the container to always call construct(p), or just not to call it. The problem, I would say is that containers were implemented (?) as too eagerly always call construct. I think in modern C++ the choice is between not calling construct or calling std::start_lifetime_as en.cppreference.com/w/cpp/memory/start_lifetime_as . whether this (or a equivalent) should be part of the allocator is a different question.Tachyphylaxis

© 2022 - 2024 — McMap. All rights reserved.