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()
.
make_unique prevents the unspecified-evaluation-order leak triggered by expressions like foo(unique_ptr<X>(new X), unique_ptr<Y>(new Y)) . (Following the advice "never say new " is simpler than "never say new , unless you immediately give it to a named unique_ptr ".)
class A {} std::unique_ptr<A> f() { return std::make_unique<A>(); } std::unique_ptr<A> a = f(); Hmm, if I remember correctly, a function in C++ returns a copy of the returned object. A function f returns a copy of std::unique_ptr constructed by std::make_unique . How is it possible?
Yes, all the elements will be value initialized by std::make_unique. This is the initialization performed when a variable is constructed with an empty initializer.
Introduction. The std::make_unique function was introduced in the C++14 standard.
These new functions are different:
make_XYZ
: Always initializes the pointed-to value ("explicit initialization", see § class.expl.init in the standard).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
ordouble
to be immediately initialized by the user in their entirety after allocation. In these cases, the value initialization performed byallocate_shared
,make_shared
, andmake_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.
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