Proper ways to use std::unique_ptr

Smart pointers remove many of the questions regarding the responsibility to free dynamically allocated memory and remedies much of the risk with memory leaks due to missing delete calls. The default smart pointer to use in C++ is the unique pointer, std::unique_ptr. There are however some do’s and don’ts when it comes to using unique pointers. In this post I will go through some common mistakes I’ve seen and what you should to instead.

1. Use make_unique instead of ’new’

There are two common ways to define a unique pointer, either by invoking the unique_ptr constructor with a raw pointer as an argument:

std::unique_ptr<T> ptr(new T);

or using the standard function make_unique:

auto ptr(std::make_unique<T>());

Of these two the latter should be your preferred choice. Why? Because there is a scenario where usage of the first approach may lead to a resource leak.

Assume you want to call a function foo that has two parameters, a unique pointer and an integer value. Also assume the integer value is a result of calling another function bar. You may have code looking like this:

foo(std::unique_ptr<T>(new T), bar());

The only guarantee here is that foo will be called after the unique pointer has been created and bar has returned its value. There is however no guarantee that the following doesn’t happen:

  1. Memory is allocated from the free store to hold T and T is constructed
  2. bar is called and it’s return value is pushed onto the stack
  3. The std::unique_ptr constructor is called and the unique pointer is created

Now, what happens here if bar throws an exception and step 3 is never done. In that case the memory allocated for T is leaked! If make_unique is used, this cannot happen:

foo(std::make_unique<T>(), bar());

If bar is called first and throws an expection, make_unique will never be called, and if make_unique is called first and bar later, and bar throws, the memory allocated for T will be free’d in the destructor of the unique pointer.

2. Don’t use ’const unique_ptr<T>&’ as parameter type to a function

There are times when you want to call a function and give it access to the object that a unique pointer holds, without transferring the ownership. Since unique pointers are, well unique, you cannot pass them by value since that would create a copy (if you try your code will fail to compile).

One solution to this problem that I see from time to time is to pass the unique pointer by reference, and since the callee shouldn’t be able to modify the unique pointer, it is passed as const:

void foo(const std::unique_ptr<T>& ptr);

void bar(std::unique_ptr<T> ptr) {
  // Some code
  if (ptr) foo(ptr);
  // Some more code that uses ptr
}

This works fine, but is unnecessary complex, and limits foo to only accept a type T that is contained by a unique_ptr. Fortunately, there is a very easy solution, just let foo take T by const reference:

void foo(const T& t);

void bar(std::unique_ptr<T> ptr) {
    // Some code
    if (ptr) foo(*ptr); // ptr is dereferenced here
    // Some more code that uses ptr
}

This small change makes foo look the way you would expect a function that works with a parameter of type T to look and the only change to bar is that ptr is dereferenced.

If you find yourself writing a function that takes a const reference to a unique ptr (or shared pointer), stop and think, what you probably want is a reference to the type of the object managed by the pointer.

Lämna en kommentar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *

Rulla till toppen