Unique_ptr in C++


Reading time: 35 minutes

A unique_ptr is like a normal pointer, except the fact that it "owns" the object to which it points, as mentioned above. The word "unique" in the title refers to the fact that at a time, only one unique_ptr can point to a given object. The object to which it points, is destroyed when the pointer itself is destroyed.

unique_ptr is defined in the <memory> header in the C++ Standard Library.

Background

Before we talk about unique_ptr, let us brush up some basics which will be required in the article.

We know that the memory layout of a C++ program contains mainly four sections: stack, heap, code section and data section. This can be shown as follows:

memory_structure

We know that C++, in addition to supporting automatic and static objects, also lets us allocate objects dynamically. This memory is allocated on the heap section of the memory. Dynamicall allocated objects have a lifetime that is independent of where they are created; they exist until they are explicitly freed.

While this freedom to allocate memory has a lot of advatanges, freeing dynamic objects can turn out to be a source of a lot of bugs. To make using dynamic objects safer, the C++ librarty defines two smart pointer types that manage dynamically allocated objects. Smart pointers ensure that the objects to which they point are automatically freed when it is appropriate to do so.

What are smart pointers?

The pointers that most of us normally deal with are often called raw pointers. In C++, dynamic memory is managed through a pair of opeartors - new and delete.

new allocates, and optionally initialises, an object in dynamic memory and returns a pointer to the object.

delete takes a pointer to a dynamic object, destroys the object and frees the associated memory.

To make using dynamic memory easier and safer, the library provides smart pointer types that manage dynamic objects. A smart pointer acts like a regular pointer for all means and purposes, with the important exception that it automatically deletes the object to which it points. In this article, we will talk about a type called unique_ptr, which, in a sense "owns" the object to which it points.

unique_ptr

unique_ptr is defined in the header in the C++ Standard Library.

A unique_ptr is like a normal pointer, except the fact that it "owns" the object to which it points, as mentioned above. The word "unique" in the title refers to the fact that at a time, only one unique_ptr can point to a given object. The object to which it points, is destroyed when the pointer itself is destroyed.

unique_ptr's can be initialised directly as follows:

unique_ptr<double> p1; // A pointer, which points to a double
unique_ptr<int> p2(new int(42)); // p2 points to an int, whose value is 42

One important fact about unique_ptr is that since it owns the object to which it points, it does not allow copy or assignment. This is to ensure that there is only one copy present and thus maintaining the uniqueness.

For example:

unique_ptr<string> p1(new string("Hello World"));

unique_ptr<string> p2(p1) // This line throws an error; no copy allowed
unique_ptr<string> p3;
p3 = p2 // Again error, as no assignment allowed

Some of the functions which are supported by unique_ptr are:

  • u = nullptr - This deletes the object to which u points; makes u null.
  • u.release() - Gives up control of the pointer u had held; returns the pointer u had held and makes u null
  • u.reset() - Deletes the object to which u points
  • u.reset(q) - If the built-in operator q is supplied, makes u point to that object
  • u.reset(nullptr) - Makes u null

Let us now look at an example, which makes use of the methods given above, to transfer ownership from one pointer to another:

Continuing from the above example:

#include<iostream>
#include<memory>

using namespace std;

int main(){

unique_ptr<string> p1(new string("Hello World"));

cout << *p1 << endl;

// transfers ownership from p1 (which was pointing to Hello World) to p2

unique_ptr<string> p2(p1.release()); // releases p1 to make it null
unique_ptr<string> p3(new string ("Hello"));

// transfers ownership from p3 to p2

p2.reset(p3.release()); // rest deletes the memory, to which p2 was earlier pointing

cout << *p2 << endl;

return 0;

}

The output of the following code is as follows:

Hello World
Hello

A more comprehensive list of methods can be found in the documentation, which can be accessed from here.

Now, although we can't copy or assign a unique_ptr, we can transfer ownership is via the move method, as mentioned here:

make_unique

Here, make_unique is used to create and return an unique_ptr to an object of the specified type. What this example does is move the ownership of an object from one pointer to the other, as we can't directly copy it.

Pitfalls to avoid

  1. When you are using a unique_ptr, make sure that you are not manually freeing the memory. Trying to do so while be equal to deleting something that is already deleted, which may lead to undesired results.

Eg:

Test *test = new Test; // creating an object of type Test
unique_ptr<Test> p1(test);
delete p1; // This will lead to undefined behaviour
  1. Make sure that there are not multiple unique_ptrs, pointing to the same object:
Test *test = new Test;
unique_ptr<Test> p1(test);
unique_ptr<Test> p2(test);

While this is syntacitcally correct, if we do this, the end result will be that both p1 and p2 will try to delete the Test object, which will result in undefined behaviour.

Conclusion

As this mentions,we should use unique_ptr when we don't intend to hold multiple references to the same object. For example, we can use it for a pointer to memory which gets allocated on entering some scope and de-allocated on exiting the scope.