Is there any use for unique_ptr with array?

C++C++11Smart PointersUnique Ptr

C++ Problem Overview


std::unique_ptr has support for arrays, for instance:

std::unique_ptr<int[]> p(new int[10]);

but is it needed? probably it is more convenient to use std::vector or std::array.

Do you find any use for that construct?

C++ Solutions


Solution 1 - C++

Some people do not have the luxury of using std::vector, even with allocators. Some people need a dynamically sized array, so std::array is out. And some people get their arrays from other code that is known to return an array; and that code isn't going to be rewritten to return a vector or something.

By allowing unique_ptr<T[]>, you service those needs.

In short, you use unique_ptr<T[]> when you need to. When the alternatives simply aren't going to work for you. It's a tool of last resort.

Solution 2 - C++

There are tradeoffs, and you pick the solution which matches what you want. Off the top of my head:

Initial size

  • vector and unique_ptr<T[]> allow the size to be specified at run-time
  • array only allows the size to be specified at compile time

Resizing

  • array and unique_ptr<T[]> do not allow resizing
  • vector does

Storage

  • vector and unique_ptr<T[]> store the data outside the object (typically on the heap)
  • array stores the data directly in the object

Copying

  • array and vector allow copying
  • unique_ptr<T[]> does not allow copying

Swap/move

  • vector and unique_ptr<T[]> have O(1) time swap and move operations
  • array has O(n) time swap and move operations, where n is the number of elements in the array

Pointer/reference/iterator invalidation

  • array ensures pointers, references and iterators will never be invalidated while the object is live, even on swap()
  • unique_ptr<T[]> has no iterators; pointers and references are only invalidated by swap() while the object is live. (After swapping, pointers point into to the array that you swapped with, so they're still "valid" in that sense.)
  • vector may invalidate pointers, references and iterators on any reallocation (and provides some guarantees that reallocation can only happen on certain operations).

Compatibility with concepts and algorithms

  • array and vector are both Containers
  • unique_ptr<T[]> is not a Container

I do have to admit, this looks like an opportunity for some refactoring with policy-based design.

Solution 3 - C++

One reason you might use a unique_ptr is if you don't want to pay the runtime cost of value-initializing the array.

std::vector<char> vec(1000000); // allocates AND value-initializes 1000000 chars

std::unique_ptr<char[]> p(new char[1000000]); // allocates storage for 1000000 chars

The std::vector constructor and std::vector::resize() will value-initialize T - but new will not do that if T is a POD.

See https://stackoverflow.com/questions/15097783/value-initialized-objects-in-c11-and-stdvector-constructor

Note that vector::reserve is not an alternative here: https://stackoverflow.com/questions/8228072/is-accessing-the-raw-pointer-after-stdvectorreserve-safe

It's the same reason a C programmer might choose malloc over calloc.

Solution 4 - C++

An std::vector can be copied around, while unique_ptr<int[]> allows expressing unique ownership of the array. std::array, on the other hand, requires the size to be determined at compile-time, which may be impossible in some situations.

Solution 5 - C++

Scott Meyers has this to say in Effective Modern C++

> The existence of std::unique_ptr for arrays should be of only intellectual interest to you, because std::array, std::vector, std::string are virtually always better data structure choices than raw arrays. About the only situation I can conceive of when a std::unique_ptr<T[]> would make sense would be when you're using a C-like API that returns a raw pointer to a heap array that you assume ownership of.

I think that Charles Salvia's answer is relevant though: that std::unique_ptr<T[]> is the only way to initialise an empty array whose size is not known at compile time. What would Scott Meyers have to say about this motivation for using std::unique_ptr<T[]>?

Solution 6 - C++

Contrary to std::vector and std::array, std::unique_ptr can own a NULL pointer.
This comes in handy when working with C APIs that expect either an array or NULL:

void legacy_func(const int *array_or_null);

void some_func() {    
    std::unique_ptr<int[]> ptr;
    if (some_condition) {
        ptr.reset(new int[10]);
    }

    legacy_func(ptr.get());
}

Solution 7 - C++

I can't disagree with the spirit of the accepted answer strongly enough. "A tool of last resort"? Far from it!

The way I see it, one of the strongest features of C++ compared to C and to some other similar languages is the ability to express constraints so that they can be checked at compile time and accidental misuse can be prevented. So when designing a structure, ask yourself what operations it should permit. All the other uses should be forbidden, and it's best if such restrictions can be implemented statically (at compile time) so that misuse results in a compilation failure.

So when one needs an array, the answers to the following questions specify its behavior:

  1. Is its size a) dynamic at runtime, or b) static, but only known at runtime, or c) static and known at compile time?
  2. Can the array be allocated on the stack or not?

And based on the answers, this is what I see as the best data structure for such an array:

       Dynamic     |   Runtime static   |         Static
Stack std::vector      unique_ptr<T[]>          std::array
Heap  std::vector      unique_ptr<T[]>     unique_ptr<std::array>

Yep, I think unique_ptr<std::array> should also be considered, and neither is a tool of last resort. Just think what fits best with your algorithm.

All of these are compatible with plain C APIs via the raw pointer to data array (vector.data() / array.data() / uniquePtr.get()).

P. S. Apart from the above considerations, there's also one of ownership: std::array and std::vector have value semantics (have native support for copying and passing by value), while unique_ptr<T[]> can only be moved (enforces single ownership). Either can be useful in different scenarios. On the contrary, plain static arrays (int[N]) and plain dynamic arrays (new int[10]) offer neither and thus should be avoided if possible - which should be possible in the vast majority of cases. If that wasn't enough, plain dynamic arrays also offer no way to query their size - extra opportunity for memory corruptions and security holes.

Solution 8 - C++

In a nutshell: it's by far the most memory-efficient.

A std::string comes with a pointer, a length, and a "short-string-optimization" buffer. But my situation is I need to store a string that is almost always empty, in a structure that I have hundreds of thousands of. In C, I would just use char *, and it would be null most of the time. Which works for C++, too, except that a char * has no destructor, and doesn't know to delete itself. By contrast, a std::unique_ptr<char[]> will delete itself when it goes out of scope. An empty std::string takes up 32 bytes, but an empty std::unique_ptr<char[]> takes up 8 bytes, that is, exactly the size of its pointer.

The biggest downside is, every time I want to know the length of the string, I have to call strlen on it.

Solution 9 - C++

I have used unique_ptr<char[]> to implement a preallocated memory pools used in a game engine. The idea is to provide preallocated memory pools used instead of dynamic allocations for returning collision requests results and other stuff like particle physics without having to allocate / free memory at each frame. It's pretty convenient for this kind of scenarios where you need memory pools to allocate objects with limited life time (typically one, 2 or 3 frames) that do not require destruction logic (only memory deallocation).

Solution 10 - C++

A common pattern can be found in some Windows Win32 API calls, in which the use of std::unique_ptr<T[]> can come in handy, e.g. when you don't exactly know how big an output buffer should be when calling some Win32 API (that will write some data inside that buffer):

// Buffer dynamically allocated by the caller, and filled by some Win32 API function.
// (Allocation will be made inside the 'while' loop below.)
std::unique_ptr<BYTE[]> buffer;

// Buffer length, in bytes.
// Initialize with some initial length that you expect to succeed at the first API call.
UINT32 bufferLength = /* ... */;

LONG returnCode = ERROR_INSUFFICIENT_BUFFER;
while (returnCode == ERROR_INSUFFICIENT_BUFFER)
{
    // Allocate buffer of specified length
    buffer.reset( BYTE[bufferLength] );
    //        
    // Or, in C++14, could use make_unique() instead, e.g.
    //
    // buffer = std::make_unique<BYTE[]>(bufferLength);
    //

    //
    // Call some Win32 API.
    //
    // If the size of the buffer (stored in 'bufferLength') is not big enough,
    // the API will return ERROR_INSUFFICIENT_BUFFER, and the required size
    // in the [in, out] parameter 'bufferLength'.
    // In that case, there will be another try in the next loop iteration
    // (with the allocation of a bigger buffer).
    //
    // Else, we'll exit the while loop body, and there will be either a failure
    // different from ERROR_INSUFFICIENT_BUFFER, or the call will be successful
    // and the required information will be available in the buffer.
    //
    returnCode = ::SomeApiCall(inParam1, inParam2, inParam3, 
                               &bufferLength, // size of output buffer
                               buffer.get(),  // output buffer pointer
                               &outParam1, &outParam2);
}

if (Failed(returnCode))
{
    // Handle failure, or throw exception, etc.
    ...
}

// All right!
// Do some processing with the returned information...
...

Solution 11 - C++

I faced a case where I had to use std::unique_ptr<bool[]>, which was in the HDF5 library (A library for efficient binary data storage, used a lot in science). Some compilers (Visual Studio 2015 in my case) provide compression of std::vector<bool> (by using 8 bools in every byte), which is a catastrophe for something like HDF5, which doesn't care about that compression. With std::vector<bool>, HDF5 was eventually reading garbage because of that compression.

Guess who was there for the rescue, in a case where std::vector didn't work, and I needed to allocate a dynamic array cleanly? :-)

Solution 12 - C++

One additional reason to allow and use std::unique_ptr<T[]>, that hasn't been mentioned in the responses so far: it allows you to forward-declare the array element type.

This is useful when you want to minimize the chained #include statements in headers (to optimize build performance.)

For instance -

> myclass.h:

class ALargeAndComplicatedClassWithLotsOfDependencies;

class MyClass {
   ...
private:
   std::unique_ptr<ALargeAndComplicatedClassWithLotsOfDependencies[]> m_InternalArray;
};

> myclass.cpp:

#include "myclass.h"
#include "ALargeAndComplicatedClassWithLotsOfDependencies.h"

// MyClass implementation goes here

With the above code structure, anyone can #include "myclass.h" and use MyClass, without having to include the internal implementation dependencies required by MyClass::m_InternalArray.

If m_InternalArray was instead declared as a std::array<ALargeAndComplicatedClassWithLotsOfDependencies>, or a std::vector<...>, respectively - the result would be attempted usage of an incomplete type, which is a compile-time error.

Solution 13 - C++

  • You need your structure to contain just a pointer for binary-compatibility reasons.
  • You need to interface with an API that returns memory allocated with new[]
  • Your firm or project has a general rule against using std::vector, for example, to prevent careless programmers from accidentally introducing copies
  • You want to prevent careless programmers from accidentally introducing copies in this instance.

There is a general rule that C++ containers are to be preferred over rolling-your-own with pointers. It is a general rule; it has exceptions. There's more; these are just examples.

Solution 14 - C++

To answer people thinking you "have to" use vector instead of unique_ptr I have a case in CUDA programming on GPU when you allocate memory in Device you must go for a pointer array (with cudaMalloc). Then, when retrieving this data in Host, you must go again for a pointer and unique_ptr is fine to handle pointer easily. The extra cost of converting double* to vector<double> is unnecessary and leads to a loss of perf.

Solution 15 - C++

They may be the rightest answer possible when you only get to poke a single pointer through an existing API (think window message or threading-related callback parameters) that have some measure of lifetime after being "caught" on the other side of the hatch, but which is unrelated to the calling code:

unique_ptr<byte[]> data = get_some_data();

threadpool->post_work([](void* param) { do_a_thing(unique_ptr<byte[]>((byte*)param)); },
                      data.release());

We all want things to be nice for us. C++ is for the other times.

Solution 16 - C++

unique_ptr<char[]> can be used where you want the performance of C and convenience of C++. Consider you need to operate on millions (ok, billions if you don't trust yet) of strings. Storing each of them in a separate string or vector<char> object would be a disaster for the memory (heap) management routines. Especially if you need to allocate and delete different strings many times.

However, you can allocate a single buffer for storing that many strings. You wouldn't like char* buffer = (char*)malloc(total_size); for obvious reasons (if not obvious, search for "why use smart ptrs"). You would rather like unique_ptr<char[]> buffer(new char[total_size]);

By analogy, the same performance&convenience considerations apply to non-char data (consider millions of vectors/matrices/objects).

Solution 17 - C++

If you need a dynamic array of objects that are not copy-constructible, then a smart pointer to an array is the way to go. For example, what if you need an array of atomics.

Solution 18 - C++

tl;dr: It's a poor man's std::dynarray.

Let's think of an std::unique_ptr<T[]> as a container. While, indeed, it is crippled by the lack of a size field, and not being directly usable as a container, it occupies a point in the "parameter space" of containers available with the standard library which is shared by no other, proper, container - not even when you add Boost to the mix.

If you'll check out my comparison of widely-available vector-like/contiguous containers, and look for the same features as those of std::unique_ptr:

  • Allocation on the Heap
  • Capacity not fixed at compile time
  • Capacity cannot be altered after construction (without clearing the container altogether)

You'll see that no other container offers all these, except std::dynarray; but that's not actually in the standard library - it was supposed to go into C++14, but ended up being rejected.

And I'm not merely speculating. Even here on SO, this is how things were described occasionally; see @KerrekSB's answer from 2013 to this question.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionfenView Question on Stackoverflow
Solution 1 - C++Nicol BolasView Answer on Stackoverflow
Solution 2 - C++PseudonymView Answer on Stackoverflow
Solution 3 - C++Charles SalviaView Answer on Stackoverflow
Solution 4 - C++Andy ProwlView Answer on Stackoverflow
Solution 5 - C++newlingView Answer on Stackoverflow
Solution 6 - C++georgeView Answer on Stackoverflow
Solution 7 - C++Violet GiraffeView Answer on Stackoverflow
Solution 8 - C++jorgbrownView Answer on Stackoverflow
Solution 9 - C++Simon FerquelView Answer on Stackoverflow
Solution 10 - C++Mr.C64View Answer on Stackoverflow
Solution 11 - C++The Quantum PhysicistView Answer on Stackoverflow
Solution 12 - C++Boris ShpunginView Answer on Stackoverflow
Solution 13 - C++Jimmy HartzellView Answer on Stackoverflow
Solution 14 - C++Romain LaneuvilleView Answer on Stackoverflow
Solution 15 - C++Simon BuchanView Answer on Stackoverflow
Solution 16 - C++Serge RogatchView Answer on Stackoverflow
Solution 17 - C++Ilia MinkinView Answer on Stackoverflow
Solution 18 - C++einpoklumView Answer on Stackoverflow