Dynamic array with std::unique_ptr

Question | Mar 9, 2020 | nextptr 

Introduction

It is a common practice in modern C++ applications to use smart-pointers for managing dynamically allocated objects. Nowadays, smart pointers are not only recommended but are considered essential for safer memory management. An std::unique_ptr is a lightweight smart-pointer that is often used to exclusively own and manage a dynamically allocated object, as follows:

struct MyClass { };

void foo() {
 //ptr is std::unique_ptr<MyClass>
 auto ptr = std::unique_ptr<MyClass>(new MyClass());
 //...
 //MyClass instance is disposed of when foo returns
}

A unique_ptr disposes of the controlled object by calling delete. But, what if we want the unique_ptr to hold a dynamically allocated array of objects? In C++, a dynamically allocated array of objects must be disposed of by calling delete[]. That's where comes a lesser-known feature of unique_ptr that it can be used to control the lifecycle of a dynamic array also:

//!!Undefined Behavior!!
auto pArr = std::unique_ptr<MyClass>(new MyClass[10]); //!Wrong

//Use a std::unique_ptr<T[]> for arrays instead.
auto pArr = std::unique_ptr<MyClass[]>(new MyClass[10]); //Correct

The unique_ptr also has an overloaded operator [] to access the array elements by index:

//Can use std::make_unique<T[]> to create std::unique_ptr<T[]>
auto ints = std::make_unique<int[]>(10); 
for(int i=0; i < 10; i++)
 ints[i] = i; //Access array element at index

At this point, you might ask why to bother about managing a dynamic array through unique_ptr when we have std::vector? In my opinion, we are better-off using std::vector in most cases. But there are a few circumstances where dynamic array through unique_ptr might be necessary or a better choice when the vector's dynamic expansion is not needed. Precisely, where exclusive ownership by unique_ptr is needed because an std::vector can be copied, or where the memory usage is a more pressing concern because an std::vector has a small memory overhead, or when we are dealing with a library that requires passing a pointer. Let's look at another example and a question on this subject in the next section.

A Question

We have a struct Blob that has some data members. The ArrayBlobPtr is a unique_ptr type that can hold an array of unique_ptr<Blob> instances. We have omitted ("_____") the type of ArrayBlobPtr in its alias declaration:

struct Blob {
 Blob(/*Data*/) {
  //...
 }
 //...
};

using ArrayBlobPtr = _____; //Type omitted

Here is a conceptual illustration of ArrayBlobPtr:


Array Unique Ptr


A function createBlobs creates a specified number of Blob objects and fills them in a provided ArrayBlobPtr, as shown:

void createBlobs(ArrayBlobPtr& blobPtrs, int n) {
 //Create Blobs
 for(int i=0; i < n; ++i) {
  blobPtrs[i] = std::make_unique<Blob>(/*Data*/);
 }
 //...
}

The function createBlobs is called as shown below:

//Create array of 10 unique_ptr<Blob>
auto blobPtrs = ArrayBlobPtr(new std::unique_ptr<Blob>[10]);

createBlobs(blobPtrs, 10);

What should be the correct type of ArrayBlobPtr in the alias declaration above ("_____")? Select below (check Explanations for details):