Say I would like to create a std::unique_ptr<int[]>
, but I would like to initialize the created array to custom values: {1,2,3,4,5}
.
I can use new
and pass the raw pointer to std::unique_ptr
constructor which will then own and manage it.
std::unique_ptr<int[]> ptr{ new int[5]{1,2,3,4,5} };
My question is, can the same somehow be done with std::make_unique
?
There are 3 overloads for std::make_unique
:
template< class T, class... Args >
unique_ptr<T> make_unique( Args&&... args ); // (1) for non-array type
template< class T >
unique_ptr<T> make_unique( std::size_t size ); // (2) for array type with unknown bounds
template< class T, class... Args >
/* unspecified */ make_unique( Args&&... args ) = delete; // (3) for array type with known bounds.
None of them supports the behavior you want (note that the third function is marked as delete
).
You can use (2) and initialize the elements of array separately, or switch to std::vector
and use (1).
hgminh's answer (in particular the part recommending vector
if possible) is correct, but I just wanted to add another option.
If the array bounds are known and fixed, not unknown bound variable length C-style arrays, you could switch from C-style arrays to std::array
to achieve this. With optimizations turned on at all, the runtime work is equivalent (at -O1
with g++
, it correctly determines that it can inline the whole thing, making it a plain allocation, followed by populating the individual elements in the newly allocated memory directly, rather than trying to make an array
on the stack, then passing it as the argument to make_unique
, which would eventually invoke the move constructor, effectively doubling the work for std::array<POD type>
). You'd just change:
std::unique_ptr<int[]> ptr{ new int[5]{1,2,3,4,5} };
to:
auto ptr = std::make_unique<std::array<int, 5>>(std::array<int, 5>{1,2,3,4,5});
Sadly, with current non-experimental feature set, this does require repeatedly specifying the type being pointed to (once to construct it, once to define the templated type of make_unique
), because make_unique
doesn't accept initializer lists, so you have to construct a temporary syntactically, even if the optimizer avoids it. For this particular case, you could use experimental features to avoid repeating yourself, but it's not much prettier (and if you don't use using
statements to avoid specifying the namespaces, actually longer):
auto ptr = std::make_unique<std::array<int, 5>>(std::experimental::make_array(1,2,3,4,5));
The main advantage and disadvantage to std::array
over C-style arrays is that, either way, the end result is std::unique_ptr<std::array<int, 5>>
, not std::unique_ptr<int[]>
; on the one hand, the size of the array being pointed to can never change (you couldn't later replace the unique_ptr
contents with a pointer to std::array<int, 6>
), but on the other hand, the size is baked in at compile time so both you and the compiler know the size.
Since the compiler knows the size, when calling functions that are templated on their argument type, you don't have to manually pass along both pointer and size. The template will be compile-time specialized to your precise size (which allows the compiler to make better optimization choices on loop unrolling or using constant loop bounds) without passing the size at all.
For functions that aren't templated and expect C-style arguments (e.g. they expect an array, and receive int*
of first element and size_t
for length), you just pass &ptr[0]
as the pointer and ptr->size()
as the length. Since the size is a compile time constant, this gets you DRY for free (no repeating the size of the array in multiple places, nor are you defining fairly useless named constants solely to avoid DRY; the size is part of the type definition, used inline, with obvious meaning in context), with no performance overhead (it should inline to the compile time size exactly as if you typed the size yourself, but without the risk of numbers getting out of sync if the array
's size is changed later).
Again, to be absolutely clear, the correct answer here is almost always "Use std::vector<int>
", which is similar to a std::unique_ptr<int[]>
that:
int[]
as neededWhen the size is not being actively changed, std::vector
can be used with C-style array APIs just fine (passing vec.data()
/&vec[0]
/&vec.at(0)
as the pointer and vec.size()
as the length), and you don't need to worry about managing resizing/reallocations (which are a pain in C++ when you can't use realloc
without giving up access to delete[]
). It can theoretically be a tiny bit slower, but in 99% of cases, it will be faster than anything that has to reimplement vector
-like behaviors from scratch (because vector
is tuned to "just work" at maximum speed, while your own code is unlikely to be a carefully tuned).
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