std::unique_ptr semantics

By | April 13, 2013

Probably the best feature introduced by C++11 is std::unique_ptr. It will automagically make sure your dynamically allocated objects are deleted when you don’t use them anymore.

In previous versions of C++, you needed to rely exclusively on documentation and conventions to ensure dynamically allocated memory was handled properly. With C++11, you can ask the compiler to help you out and enforce ownership semantics. Take the following code for example, you need to ensure you call delete when you are done with the object created by function factory(). The compiler will not complain if you forget and you end up with a memory leak.

What can std::unique_ptr do for you ?

With std::unique_ptr, you cannot casually forget to delete the pointer. std::unique_ptr owns the allocated object unless this ownership is explicitly transferred to another entity (eg. another unique_ptr, a std::shared_ptr). When a std::unique_ptr goes out of scope, it will delete any object it owns.

The most explicit way of transferring ownership is via function std::move(). Using the same above example, we can see how it works.

You cannot directly assign an instance of std::unique_ptr to another. This rule ensures that no two unique_ptr can claim ownership of the same dynamically allocated object. It prevents deleting an object multiple times.
This is the magic of std::unique_ptr, you always know who owns the pointed value. By consistently using unique_ptr, you know at a glance when you own a heap object and when you yield ownership to another. Moreover, this rule is enforced by the compiler — the less you need to worry about, the better.

How do you use std::unique_ptr ?

std::unique_ptr helps you express your intent with regards to ownership transfer. Transfer of ownership occurs in the following general cases :

  • Returning a dynamically allocated object from a function
  • Passing a such an object to a function

Returning a std::unique_ptr

Returning a heap-allocated object from a function requires that the caller delete it when it is done with.

By returning a unique_ptr, you ensure the caller takes ownership of the pointed object. In the example below, the signature of createVector() ensures the caller takes the responsibility for releasing the created vector. Normal flow of execution guarantees memory will be released whenever the returned unique_ptr is destroyed.

Passing a std::unique_ptr to a function

A function taking a unique_ptr takes definitive ownership of the pointed object. The caller will not even have the ability to access the pointed object after the function call.
Notice the call to function std::move(), it is required and helps you actually see that ownership is transferred to the function.

Passing a const reference to a unique_ptr

The called function can use the pointed object but may not interfere with its lifetime. The caller function keeps ownership of the pointed object.

In my opinion, there is no real benefit to using a const reference to a unique_ptr from simply using a raw pointer to the object. On the contrary, it introduces a constraint on the caller that it must manage the pointed object through a unique_ptr. But what if your caller context requires you have shared ownership (std::shared_ptr)? Since we mostly care about whether ownership is transfered (not how ownership is cared for), a raw pointer is perfect.

Passing a non-const reference to a unique_ptr

This is, in my opinion, the most complex situation. The called function may or may not take ownership of the passed pointer. “may or may not” is really part of the functions contract. If the called function uses std::move() on the passed unique_ptr, it effectively takes onwnership and once it gives control back to the caller, the caller does not own the pointer anymore, it doesn’t even know the value of the pointer anymore. On the other hand, if the callee merely uses the provided pointer without moving it, the caller still owns the pointed object.

Conclusion

C++11 provides us with a great tool for managing the lifetime of dynamically allocated object. When used consistently, std::unique_ptr lets your code really express when ownership of a dynamic object is transferred and in which direction directly. The compiler will even see that the semantics are respected at compile-time.

However, they are of no use when you don’t intend to express ownership semantics. Pass raw pointers whenever you can but make sure that you use std::unique_ptr or another similar smart pointer when you mean to transfer ownership.

Leave a Reply