Should allocator construct() default initialize instead of value initializing? - c++

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]{}.

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.

Related

Why CTAD failed for std::unique_ptr(new int()) in C++17? [duplicate]

When you have class template argument deduction available from C++17, why can't you deduce the template arguments of std::unique_ptr? For example, this gives me an error:
std::unique_ptr smp(new D);
That says "Argument list of class template is missing".
Shouldn't the template arguments (at least the pointer type) be deducable?
See this:
any declaration that specifies initialization of a variable and
variable template
Lets look at new int and new int[10]. Both of those return an int*. There is no way to tell if you should have unique_ptr<int> or unique_ptr<int[]>. That right there is enough not to provide any sort of deduction guide.
I'm not going to repeat the rationale in #NathanOliver's great answer, I'm just going to mention the how of it, the mechanics, which is what I think you are also after. You are right that if the constructor of unique_ptr looked merely like...
explicit unique_ptr( T* ) noexcept;
... it'd be possible to deduce T. The compiler generated deduction guide would work just fine. And that would be a problem, like Nathan illustrates. But the constructor is specified like this...
explicit unique_ptr( pointer p ) noexcept;
... where the alias pointer is specified as follows:
pointer : std::remove_reference<Deleter>::type::pointer if that
type exists, otherwise T*. Must satisfy NullablePointer.
That specification essentially means that pointer must be an alias to __some_meta_function<T>::type. Everything on the left of ::type is a non-deduced context, which is what prevents the deduction of T from pointer. That's how these sort of deduction guides could be made to fail even if pointer needed to be T* always. Just by making it a non-deduced context will prevent the viability of any deduction guide produced from that constructor.
So this is a side effect from those olden times at the beginning of C++, when the standard makers decided to have two different delete and delete[] operators for pointers to objects and pointers to arrays of objects.
In these modern times of C++, where we have templates (they weren't there from the beginning), std::array (for fixed sized arrays), inititalizer lists (for static fixed sized arrays) and std::vector (for dynamically sized arrays), almost nobody will need the delete[] operator anymore. I have never used it, and I wouldn't be surprised, if the vast majority of the readers of this question have not used it, either.
Removing int* array = new int[5]; in favour of auto* array = new std::array<int, 5>; would simplify things and would enable safe conversion of pointers to std::unique_ptr and std::shared_ptr. But it would break old code, and so far, the C++ standard maintainers have been very keen on backwards compatibility.
Nobody stops you, though, from writing a small inlined templated wrapper function:
template<typename T>
std::unique_ptr<T> unique_obj_ptr(T* object) {
static_assert(!std::is_pointer<T>::value, "Cannot use pointers to pointers here");
return std::unique_ptr<T>(object);
}
Of course, you can also create a similiar function shared_obj_ptr() to create std::shared_ptrs, and if you really need them, you can also add unique_arr_ptr() and shared_arr_ptr().

Is GCC9 avoiding valueless state of std::variant allowed?

I recently followed a Reddit discussion which lead to a nice comparison of std::visit optimization across compilers. I noticed the following: https://godbolt.org/z/D2Q5ED
Both GCC9 and Clang9 (I guess they share the same stdlib) do not generate code for checking and throwing a valueless exception when all types meet some conditions. This leads to way better codegen, hence I raised an issue with the MSVC STL and was presented with this code:
template <class T>
struct valueless_hack {
struct tag {};
operator T() const { throw tag{}; }
};
template<class First, class... Rest>
void make_valueless(std::variant<First, Rest...>& v) {
try { v.emplace<0>(valueless_hack<First>()); }
catch(typename valueless_hack<First>::tag const&) {}
}
The claim was, that this makes any variant valueless, and reading the docu it should:
First, destroys the currently contained value (if any). Then
direct-initializes the contained value as if constructing a value of
type T_I with the arguments std::forward<Args>(args).... If an
exception is thrown, *this may become valueless_by_exception.
What I don't understand: Why is it stated as "may"? Is it legal to stay in the old state if the whole operation throws? Because this is what GCC does:
// For suitably-small, trivially copyable types we can create temporaries
// on the stack and then memcpy them into place.
template<typename _Tp>
struct _Never_valueless_alt
: __and_<bool_constant<sizeof(_Tp) <= 256>, is_trivially_copyable<_Tp>>
{ };
And later it (conditionally) does something like:
T tmp = forward(args...);
reset();
construct(tmp);
// Or
variant tmp(inplace_index<I>, forward(args...));
*this = move(tmp);
Hence basically it creates a temporary, and if that succeeds copies/moves it into the real place.
IMO this is a violation of "First, destroys the currently contained value" as stated by the docu. As I read the standard, then after a v.emplace(...) the current value in the variant is always destroyed and the new type is either the set type or valueless.
I do get that the condition is_trivially_copyable excludes all types that have an observable destructor. So this can also be though as: "as-if variant is reinitialized with the old value" or so. But the state of the variant is an observable effect. So does the standard indeed allow, that emplace does not change the current value?
Edit in response to a standard quote:
Then initializes the contained value as if direct-non-list-initializing a value of type TI with the arguments std​::​forward<Args>(args)....
Does T tmp {std​::​forward<Args>(args)...}; this->value = std::move(tmp); really count as a valid implementation of the above? Is this what is meant by "as if"?
I think the important part of the standard is this:
From https://timsong-cpp.github.io/cppwp/n4659/variant.mod#12
23.7.3.4 Modifiers
(...)
template
variant_alternative_t>& emplace(Args&&... args);
(...) If an exception is thrown during the initialization of the contained value, the variant might not hold a value
It says "might" not "must". I would expect this to be intentional in order to allow implementations like the one used by gcc.
As you mentioned yourself, this is only possible if the destructors of all alternatives are trivial and thus unobservable because destroying the previous value is required.
Followup question:
Then initializes the contained value as if direct-non-list-initializing a value of type TI with the arguments std​::​forward<Args>(args)....
Does T tmp {std​::​forward(args)...}; this->value = std::move(tmp); really count as a valid implementation of the above? Is this what is meant by "as if"?
Yes, because for types that are trivially copyable there is no way to detect the difference, so the implementation behaves as if the value was initialized as described. This would not work if the type was not trivially copyable.
So does the standard indeed allow, that emplace does not change the
current value?
Yes. emplace shall provide the basic guarantee of no leaking (i.e., respecting object lifetime when construction and destruction produce observable side effects), but when possible, it is allowed to provide the strong guarantee (i.e., the original state is kept when an operation fails).
variant is required to behave similarly to a union — the alternatives are allocated in one region of suitably allocated storage. It is not allowed to allocate dynamic memory. Therefore, a type-changing emplace has no way to keep the original object without calling an additional move constructor — it has to destroy it and construct the new object in place of it. If this construction fails, then the variant has to go to the exceptional valueless state. This prevents weird things like destroying a nonexistent object.
However, for small trivially copyable types, it is possible to provide the strong guarantee without too much overhead (even a performance boost for avoiding a check, in this case). Therefore, the implementation does it. This is standard-conforming: the implementation still provides the basic guarantee as required by the standard, just in a more user-friendly way.
Edit in response to a standard quote:
Then initializes the contained value as if
direct-non-list-initializing a value of type TI with the arguments
std​::​forward<Args>(args)....
Does T tmp {std​::​forward<Args>(args)...}; this->value =
std::move(tmp); really count as a valid implementation of the above?
Is this what is meant by "as if"?
Yes, if the move assignment produces no observable effect, which is the case for trivially copyable types.

What does std::make_unique_for_overwrite() do vis-a-vis std::make_unique()?

It appears that in C++20, we're getting some additional utility functions for smart pointers, including:
template<class T> unique_ptr<T> make_unique_for_overwrite();
template<class T> unique_ptr<T> make_unique_for_overwrite(size_t n);
and the same for std::make_shared with std::shared_ptr. Why aren't the existing functions:
template<class T, class... Args> unique_ptr<T> make_unique(Args&&... args); // with empty Args
template<class T> unique_ptr<T> make_unique(size_t n);
enough? Don't the existing ones use the default constructor for the object?
Note: In earlier proposals of these functions, the name was make_unique_default_init().
These new functions are different:
Original make_XYZ: Always initializes the pointed-to value ("explicit initialization", see § class.expl.init in the standard).
New make_XYZ_for_overwrite: Performs "default initialization" of the pointed-to value (see § dcl.init, paragraph 7 in the standard); on typical machines, this means effectively no initialization for non-class, non-array types. (Yes, the term is a bit confusing; please read the paragraph at the link.)
This is a feature of plain vanilla pointers which was not available with the smart pointer utility functions: With regular pointers you can just allocate without actually initializing the pointed-to value:
new int
For unique/shared pointers you could only achieve this by wrapping an existing pointer, as in:
std::unique_ptr<int[]>(new int[n])
now we have a wrapper function for that.
Note: See the relevant ISO C++ WG21 proposal as well as this SO answer
allocate_shared, make_shared, and make_unique all initialize the underlying object by performning something equivalent to new T(args...). In the zero-argument case, that reduces to new T() - which is to say, it performs value initialization. Value initialization in many cases (including scalar types like int and char, arrays of them, and aggregates of them) performs zero initialization - which is to say, that is actual work being done to zero out a bunch of data.
Maybe you want that and that is important to your application, maybe you don't. From P1020R1, the paper that introduced the functions originally named make_unique_default_init, make_shared_default_init, and allocate_shared_default_init (these were renamed from meow_default_init to meow_for_overwrite during the national ballot commenting process for C++20):
It is not uncommon for arrays of built-in types such as unsigned char or double to be immediately initialized by the user in their entirety after allocation. In these cases, the value initialization performed by allocate_shared, make_shared, and make_unique is redundant and hurts performance, and a way to choose default initialization is needed.
That is, if you were writing code like:
auto buffer = std::make_unique<char[]>(100);
read_data_into(buffer.get());
The value initialization performed by make_unique, which would zero out those 100 bytes, is completely unnecessary since you're immediately overwriting it anyway.
The new meow_for_overwrite functions instead perform default initialization since the memory used will be immediately overwritten anyway (hence the name) - which is to say the equivalent of doing new T (without any parentheses or braces). Default initialization in those cases I mentioned earlier (like int and char, arrays of them, and aggregates of them) performs no initialization, which saves time.
For class types that have a user-provided default constructor, there is no difference between value initialization and default initialization: both would just invoke the default constructor. But for many other types, there can be a large difference.

Value-Initialized Objects in C++11 and std::vector constructor

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?
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());
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;

is uninitialized_copy/fill(In first, In last, For dest, A &a) an oversight in the c++ standard?

I like to know how things work and as such have been delving deep into the c++ standard library. Something occurred to me the other day.
It is required that containters (for example: std::vector<int, std::allocator<int> >) use the allocator specified for allocations. Specifically the standard says:
23.1.8
Copy constructors for all container types defined in this clause copy
an allocator argument from their respective first parameters. All
other constructors for these container types take an Allocator&
argument (20.1.5), an allocator whose value type is the same as the
container’s value type. A copy of this argument is used for any memory
allocation performed, by these constructors and by all member
functions, during the lifetime of each container object. In all
container types defined in this clause, the member get_allocator()
returns a copy of the Allocator object used to construct the
container.
Also later in the standard it says (in a few different spots but i'll pick one) things like this:
explicit deque(size_type n, const T& value = T(), const Allocator& = Allocator());
Effects: Constructs a deque with n copies of value,
using the specified allocator.
OK, so on to my question.
Let's take std::vector as an example, the natural and efficient way to implement something like:
vector<T, A>::vector(const vector& x)
might look something like this:
template <class T, class A>
vector<T, A>::vector(const vector& x) {
pointer p = alloc_.allocate(x.size());
std::uninitialized_copy(x.begin(), x.end(), p);
first_ = p;
last_ = p + x.size();
end_ = p + x.size();
}
specifically, we allocate some memory and then copy construct all the members in place. Not bothering to do something like new value_type[x.size()] because that would default construct the array only to overwrite it!.
but, this doesn't use the allocator to do the copy construction...
I could manually write a loop which does something like this:
while(first != last) {
alloc_.construct(&*dest++, *first++);
}
but that's a waste, it's nearly identical to std::uninitialized_copy, the only difference is that is uses the allocator instead of placement new.
So, would you consider it an oversight that the standard doesn't have the (seemingly obvious to me) set of functions like these:
template <class In, class For, class A>
For uninitialized_copy(In first, In last, For dest, A &a);
template <class In, class Size, class For, class A>
For uninitialized_copy_n(In first, Size count, For dest, A &a);
template <class For, class T, class A>
void uninitialized_fill(For first, For last, const T& x, A &a);
template <class For, class Size, class T, class A>
void uninitialized_fill_n(For first, Size count, const T& x, A &a);
I would imagine that these types of functions (even though they are trivial to implement manually... until you try to make them exception safe) would prove fairly useful if people want to implement there own containers and such and make efficient use of copy construction while using allocators.
Thoughts?
I'm not sure whether we could call it an "oversight", per se.
No, you can't provide your own allocator to these specialised algorithms. But then there are other things that the standard doesn't contain, either.
#MarkB identifies a very good reason that the standard shouldn't do this (that the range has no knowledge of the container's allocator). I'd go so far as to say it's just an inherent limitation.
You can always re-invent uninitialized_copy for your needs, knowing what the allocator should be. It's just a two-line for loop.
If these functions were free-functions, I can't see any way that the compiler could detect allocator mismatches since the allocator type isn't retained by the iterator. This in turn could result in a variety of hard-to-find problems.
Yes, I think it is a (big) oversight, because information about the allocator is lost otherwise. The allocator is, in the current protocols, the only one that knows how to exactly construct an object in memory.
Now Boost includes alloc_construct, alloc_destroy https://www.boost.org/doc/libs/1_72_0/libs/core/doc/html/core/alloc_construct.html
which at least can help a bit implementing generic versions of uninitialized_copy/fill/etc(Alloc a, ...).