Table of Contents

Modern C++ stresses the use of RAII objects to manage resources. One of the easiest ways is just to start using unique_ptr across your code.

Let’s see how we can leverage this smart pointer type. I’ve come up with 5 (or more?) reasons where unique_ptr shines.

Intro  

One of my favourite features of modern C++ is smart pointers. They are maybe not magic bullets that solve all resource management problems. Still, they play a significant role in enforcing safety. By using them, you can avoid memory leaks, access violations errors and be sure the object clean up is done right.

While shared_ptr and weak_ptr are more complex, unique_ptr seems to be a perfect replacement for owning raw pointers. Not to mention is the fact that this pointer type is mostly a compile time “wrapper” and it cost almost nothing in the runtime.

So for today, I’ve selected five… six strong points of unique_ptr. Think where, in your project, could you immediately apply such pointers. Or maybe you have done that already? :)

Let’s start!

0. Just use the stack  

I know we’re talking about memory allocations, pointers, etc, etc… but at the beginning, I’d like to make a little remark.

Sometimes using the heap is necessary, for example when your object needs to live longer than the current scope, or when you don’t know the exact size of the object (it needs to be computed in the runtime), or when your object is large.

Still, think about the stack… maybe your object can be put there? Often, I’ve seen that an object is allocated on the heap, while it could be safely placed on the stack.

The stack is faster, safer and just works :)

The default stack size is around 1MB in MSVC (platform specific), that included memory for the call stack, params, etc…. but if your object is not super large and you’re not doing any deep recursions, it should be good enough.

Also remember that containers, like vector, list, or even string will use heap anyway - they will take some space on the stack (like for small buffer optimization, or small string optimization), but they will allocate a chunk for the elements on the free store.

Let’s move to the first point now.

1. unique_ptr replaces auto_ptr  

Maybe you’re a bit insulted by this point. I know you’re not that person that still uses auto_ptr… but let’s be honest, we all know auto_ptr is still hiding in our projects :)

Such pointers lurk from their caves and see a chance to cause bugs in the system!

But let’s be serious this time:

With C++11 auto_ptr got deprecated and in C++17 it’s removed from the standard. So we “got” 6 years to abandon and refactor old code.

For example, here’s what happens when compiling a code with auto_ptr with MCSV and conformance flag set to C++17 (/std:c++latest):

error C2039: 'auto_ptr': is not a member of 'std'

Notice that it’s an error, not a warning.

So what should you use instead?

Of course, it’s the hero of our story: unique_ptr!

The main advantage of using modern smart pointers is the ability to express ownership transfer properly. With auto_ptr you could easily get into troubles. Take a look here:

void doSomethig(std::auto_ptr<Test> myPtr) {
    myPtr->m_value = 11;
}

void AutoPtrTest() {
    std::auto_ptr<Test> myTest(new Test());
    doSomethig(myTest);
    myTest->m_value = 10;
}

What happens after doSomething() returns and we want to access myTest->m_value?

Or here’s the full (be careful!) example:

#include <memory>
#include <iostream>

class Test
{
public:
   Test():m_value(0) { std::cout << "Test::Test\n"; }
   ~Test() { std::cout << "Test::~Test destructor\n"; }

   int m_value;
};

void doSomethig(std::auto_ptr<Test> myPtr) {
    myPtr->m_value = 11;
}

void AutoPtrTest() {
    std::auto_ptr<Test> myTest(new Test());
    doSomethig(myTest);
    myTest->m_value = 10; // ??
}

int main()
{
	AutoPtrTest();
}

In other words: auto_ptr is limited - mostly by not having move semantics supported at the time it was introduced. It’s also not reference counted (as shared pointers), so its use is somewhat cumbersome.

How to refactor your code? from WG21 N4190:

auto_ptr has been superseded by unique_ptr. Any code using auto_ptr can be mechanically converted to using unique_ptr, with move() inserted whenever auto_ptr was being “copied”. clang-modernize’s Replace-AutoPtr Transform does exactly this.

And here’s the link to clang: clang-tidy - modernize-replace-auto-ptr — Extra Clang Tools.

2. unique_ptr hides raw new and delete  

A few days before I started writing this text I’ve seen the following Abseil/Google C++ guideline

abseil / Tip of the Week #126: make_unique is the new new

Plus there are a Core Guideline suggestions:

R.11: Avoid calling new and delete explicitly

The pointer returned by new should belong to a resource handle (that can call delete). If the pointer returned by new is assigned to a plain/naked pointer, the object can be leaked.

In other words: one of the parts of modern C++ is to avoid using raw new and delete. Instead, we should wrap allocated memory into RAII objects.

The main reasons why naked new and delete might cause harm are the following:

  • You need to remember to release the memory. It’s easy when it’s inside a little function… but for larger scopes it gets tricky. Moreover, you might need to interleave with other resources.
  • Naked pointers doesn’t show whose the real owner of a resource.

Whenever you see new try to replace it with a unique_ptr and make_unique.

Recently @Modernescpp the author also discusses more rules for allocation and deallocation, so read more here: C++ Core Guidelines: Rules for Allocating and Deallocating (R.13)

3. Control the ownership of a pointer  

MyType* pObject;

What can you deduce from the statement above?

And how about:

std::unique_ptr<MyType> pObject;

In modern C++ we head towards using raw pointers as a way to observe things, but the owners are in the form of smart pointers (or other RAII types).

Still, especially in legacy code, you cannot be sure what a raw pointer means. Is it for observation, or it’s an owning pointer?

Knowing who the owner is playing a core part when releasing the resource. It’s especially seen in factory (or builder) functions that return allocated objects:

MyType* BuildObject()
{
    MyType* pObj = new MyType();
    complexBuild(pObj);
    return pObj;
}

// ...
auto pObj = BuildObject();

The only way we had to express who should release the memory was through some guidelines or comments. But the compiler wouldn’t help in case of a bug and a leak.

With:

unique_ptr<MyType> BuildObject()
{
    auto pObj = make_unique<MyType>();
    complexBuild(pObj.get());
    return pObj;
}

// ...
auto pObj = BuildObject();

Now, the ownership is inside pObj, and it’s released when pObj goes out of scope.

Passing to functions  

Having better control over the ownership of the object is also handy when you want to pass resources to other functions.

Sometimes, when you want to “observe” an object it’s enough to pass a raw pointer or a reference (const reference?) - use .get() from unique pointer to get the raw pointer, or *ptr to get the reference.

There are also functions that require full ownership, they are called “sink functions”. In order to pass unique pointer to such function you need to use std::move.

unique_ptr<MyObj> myPtr ...;
fooAsRawPtr(myPtr.get());
fooAsRef(*myPtr);
fooExclusive(std::move(myPtr));

I wrote about Sink functions a few months ago: Modernize: Sink Functions.

4. Use inside functions  

Every time you have to allocate an object on the free store, it’s useful to wrap it intounique_ptr.

A canonical example:

void FuncMightLeak()
{
    MyType* pFirst = new MyType();

    if (!process())
    {
        delete pFirst;
        return;
    }

    MyType* pSecond = new MyType();

    if (!processSecond())
    {
        delete pFirst;
        delete pSecond;
        return;
    }

    process();
    delete pFirst;
    delete pSecond;
}

Do you see how much effort needed to clean up the memory correctly? And it’s such a simple code - in real-life resource management code might be much more complex.

Moreover, as suggested by KrzaQin comments, if process() throws, then we’ll get a leak, as even our manually crafted deletions won’t save us.

And here’s the converted version that uses unique_ptr:

void FuncNoLeaks()
{
    auto pFirst = std::make_unique<MyType>();

    if (!process())
        return;

    auto pSecond = std::make_unique<MyType>();

    if (!processSecond())
        return;

    process();
}

It looks almost like if the pointers were allocated on the stack.

What’s more, this could also work for other types of resources. So whenever you have to hold such things, you can think about RAII approach (using a smart pointer, custom RAII, custom deleter etc.)

Remark: the examples used memory allocated on the heap… but think if such objects can be allocated on the stack (see my 0th point). Not only it’s more straightforward but usually faster. Heap might be useful for huge objects (that wouldn’t fit into the stack space).

Play with the above code here:

#include <memory>
#include <iostream>

class MyType
{
public:
	MyType() { std::cout << "MyType::MyType\n"; }
	~MyType() { std::cout << "MyType::~MyType\n"; }
};

bool process() { return true; }
bool processSecond() { return true; }

void FuncMightLeak()
{
	std::cout << "FuncMightLeak\n";
    MyType* pFirst = new MyType();
    
    if (!process())
    {
        delete pFirst;
        return;
    }
    
    MyType* pSecond = new MyType();
    
    if (!processSecond())
    {
        delete pFirst;
        delete pSecond;
        return;
    }
    
    process();
    delete pFirst;
    delete pSecond;
}


void FuncNoLeaks()
{
	std::cout << "FuncNoLeaks\n";
    auto pFirst = std::make_unique<MyType>();
    
    if (!process())
        return;
    
    auto pSecond = std::make_unique<MyType>();
    
    if (!processSecond())
        return;
    
    process();
}

int main()
{
	FuncMightLeak();
	FuncNoLeaks();
}

Code @Compiler Explorer

5. Implement “pimpl” idom with unique_ptr  

“Pointer to implementation” - “pimpl” is a conventional technique to reduce compile time dependencies. Briefly, it hides all (or some) details of the class implementation with some other type (that is only forward declared). Such technique allows changing the implementation without the need to recompile client code.

Here’s some code:

In the above example NetworkManager has a private implementation - NetManImpl which is hidden from the client code (main()). NetManImpl is implemented solely in a cpp file, so any changes to that class won’t cause the need to recompile client code - cpp_pimpl_client.cpp.

While this approach might sound like a lot of redundancy and redirection (see implementations of Connect or Disconnect methods that just redirects the calls) it makes modules physically independent.

You can read more about Pimpl here: The Pimpl Pattern - what you should know - C++ Stories

Bonus - 6th reason!  

By default, unique pointer uses delete (or delete[]) to free the allocated memory. This mechanism is good enough in probably like 99% of cases, but if you like, you might alter this behaviour and provide a custom deleter.

The thing to remember, according to the cppreference:

If get() == nullptr there are no effects. Otherwise, the owned object is destroyed via get_deleter()(get()). Requires that get_deleter()(get()) does not throw exceptions.

In other words, the deleter is called when the managed pointer is not null.

Also, as deleter is part of the type of the unique pointer and may contribute to its size (if that’s a function pointer then it will take some space, if it’s only a stateless functor, then not).

Here’s an example:

In the above example, we need to provide a custom deleter to clean up ‘LegacyList’ objects properly.: ReleaseElements method must be called before the object is deleted. The cleanup procedure is wrapped into a stateless functor object:

struct LegacyListDeleterFunctor {  
    void operator()(LegacyList* p) {
        p->ReleaseElements(); 
        delete p;
    }
};

If you like to read more about custom deleters see:

If you're interested in smart pointers - have a look at my handy reference card. It covers everything you need to know about unique_ptr, shared_ptr and weak_ptr, wrapped in a beautiful PDF:

Summary  

I hope you’re convinced to start using unique_ptr in your projects :)

With this list, I’ve shown how this type of smart pointers leads to memory safety, code clarity, expressiveness and even much shorter code.

Do you have any cool examples how you used unique_ptr? Share your stories and observations in the comments below the article.