std::unique_ptr

At the chapter's beginning, we discussed how pointers can lead to bugs and memory leaks in certain situations. For example, this can happen when a function returns early or throws an exception, and the pointer isn't properly deleted.

#include <iostream>

void processFile()
{
    auto* file{new FileHandle()};

    int status{};
    std::cout << "Enter status code: ";
    std::cin >> status;

    if (status == 0)
        throw std::runtime_error("File error"); // function returns early, file won't be deleted!

    // do stuff with file here

    delete file;
}

Now that we've covered move semantics fundamentals, we can return to smart pointer classes. Although smart pointers can offer other features, the defining characteristic of a smart pointer is that it manages a dynamically allocated resource provided by the user of the smart pointer, and ensures the dynamically allocated object is properly cleaned up at the appropriate time (usually when the smart pointer goes out of scope).

Because of this, smart pointers should never be dynamically allocated themselves (otherwise, there's risk that the smart pointer may not be properly deallocated, meaning the object it owns wouldn't be deallocated, causing a memory leak). By always allocating smart pointers on the stack (as local variables or composition members of a class), we're guaranteed that the smart pointer will properly go out of scope when the function or object it's contained within ends, ensuring the object the smart pointer owns is properly deallocated.

The C++11 standard library ships with 4 smart pointer classes: std::auto_ptr (removed in C++17), std::unique_ptr, std::shared_ptr, and std::weak_ptr. std::unique_ptr is by far the most used smart pointer class, so we'll cover that one first. In following lessons, we'll cover std::shared_ptr and std::weak_ptr.

std::unique_ptr

std::unique_ptr is the C++11 replacement for std::auto_ptr. It should be used to manage any dynamically allocated object that isn't shared by multiple objects. That is, std::unique_ptr should completely own the object it manages, not share that ownership with other classes. std::unique_ptr lives in the header.

Let's look at a simple smart pointer example:

#include <iostream>
#include <memory> // for std::unique_ptr

class FileHandle
{
public:
    FileHandle() { std::cout << "File opened\n"; }
    ~FileHandle() { std::cout << "File closed\n"; }
};

int main()
{
    // allocate a FileHandle object and have it owned by std::unique_ptr
    std::unique_ptr<FileHandle> file{new FileHandle()};

    return 0;
} // file goes out of scope here, and the allocated FileHandle is destroyed

Because the std::unique_ptr is allocated on the stack here, it's guaranteed to eventually go out of scope, and when it does, it will delete the FileHandle it's managing.

Unlike std::auto_ptr, std::unique_ptr properly implements move semantics.

#include <iostream>
#include <memory> // for std::unique_ptr
#include <utility> // for std::move

class FileHandle
{
public:
    FileHandle() { std::cout << "File opened\n"; }
    ~FileHandle() { std::cout << "File closed\n"; }
};

int main()
{
    std::unique_ptr<FileHandle> file1{new FileHandle{}}; // FileHandle created here
    std::unique_ptr<FileHandle> file2{}; // Start as nullptr

    std::cout << "file1 is " << (file1 ? "not null\n" : "null\n");
    std::cout << "file2 is " << (file2 ? "not null\n" : "null\n");

    // file2 = file1; // Won't compile: copy assignment is disabled
    file2 = std::move(file1); // file2 assumes ownership, file1 is set to null

    std::cout << "Ownership transferred\n";

    std::cout << "file1 is " << (file1 ? "not null\n" : "null\n");
    std::cout << "file2 is " << (file2 ? "not null\n" : "null\n");

    return 0;
} // FileHandle destroyed here when file2 goes out of scope

This prints:

File opened
file1 is not null
file2 is null
Ownership transferred
file1 is null
file2 is not null
File closed

Because std::unique_ptr is designed with move semantics in mind, copy initialization and copy assignment are disabled. If you want to transfer the contents managed by std::unique_ptr, you must use move semantics. In the program above, we accomplish this via std::move (which converts file1 into an r-value, triggering a move assignment instead of a copy assignment).

Accessing the managed object

std::unique_ptr has an overloaded operator* and operator-> that can be used to return the resource being managed. Operator* returns a reference to the managed resource, and operator-> returns a pointer.

Remember that std::unique_ptr may not always be managing an object -- either because it was created empty (using the default constructor or passing in a nullptr as the parameter), or because the resource it was managing got moved to another std::unique_ptr. So before we use either of these operators, we should check whether the std::unique_ptr actually has a resource. Fortunately, this is easy: std::unique_ptr has a cast to bool that returns true if the std::unique_ptr is managing a resource.

Here's an example:

#include <iostream>
#include <memory> // for std::unique_ptr

class FileHandle
{
public:
    FileHandle() { std::cout << "File opened\n"; }
    ~FileHandle() { std::cout << "File closed\n"; }
};

std::ostream& operator<<(std::ostream& out, const FileHandle&)
{
    out << "I am a file handle";
    return out;
}

int main()
{
    std::unique_ptr<FileHandle> file{new FileHandle{}};

    if (file) // use implicit cast to bool to ensure file contains a FileHandle
        std::cout << *file << '\n'; // print the FileHandle that file is owning

    return 0;
}

This prints:

File opened
I am a file handle
File closed

In the above program, we use the overloaded operator* to get the FileHandle object owned by std::unique_ptr file, which we then send to std::cout for printing.

std::unique_ptr and arrays

Unlike std::auto_ptr, std::unique_ptr is smart enough to know whether to use scalar delete or array delete, so std::unique_ptr is okay to use with both scalar objects and arrays.

However, std::array or std::vector (or std::string) are almost always better choices than using std::unique_ptr with a fixed array, dynamic array, or C-style string.

Best Practice
Favor std::array, std::vector, or std::string over a smart pointer managing a fixed array, dynamic array, or C-style string.

std::make_unique

C++14 comes with an additional function named std::make_unique(). This templated function constructs an object of the template type and initializes it with the arguments passed into the function.

#include <memory> // for std::unique_ptr and std::make_unique
#include <iostream>

class Temperature
{
private:
    int m_celsius{0};

public:
    Temperature(int celsius = 0)
        : m_celsius{celsius}
    {
    }

    friend std::ostream& operator<<(std::ostream& out, const Temperature& temp)
    {
        out << temp.m_celsius << " degrees C";
        return out;
    }
};

int main()
{
    // Create a single dynamically allocated Temperature with value 25 degrees
    // We can also use automatic type deduction to good effect here
    auto temp1{std::make_unique<Temperature>(25)};
    std::cout << *temp1 << '\n';

    // Create a dynamically allocated array of Temperature of length 4
    auto temp2{std::make_unique<Temperature[]>(4)};
    std::cout << temp2[0] << '\n';

    return 0;
}

The code above prints:

25 degrees C
0 degrees C

Use of std::make_unique() is optional, but is recommended over creating std::unique_ptr yourself. This is because code using std::make_unique is simpler, and it also requires less typing (when used with automatic type deduction). Furthermore, in C++14 it resolves an exception safety issue that can result from C++ leaving the order of evaluation for function arguments unspecified.

Best Practice
Use std::make_unique() instead of creating std::unique_ptr and using new yourself.

The exception safety issue in more detail

For those wondering what the "exception safety issue" mentioned above is, here's a description.

Consider an expression like this:

some_function(std::unique_ptr<T>(new T), function_that_can_throw_exception());

The compiler is given a lot of flexibility in how it handles this call. It could create a new T, then call function_that_can_throw_exception(), then create the std::unique_ptr that manages the dynamically allocated T. If function_that_can_throw_exception() throws an exception, then the T that was allocated will not be deallocated, because the smart pointer to do the deallocation hasn't been created yet. This leads to T being leaked.

std::make_unique() doesn't suffer from this problem because the creation of object T and the creation of the std::unique_ptr happen inside the std::make_unique() function, where there's no ambiguity about order of execution.

This issue was fixed in C++17, as evaluation of function arguments can no longer be interleaved.

Returning std::unique_ptr from a function

std::unique_ptr can be safely returned from a function by value:

#include <memory> // for std::unique_ptr

std::unique_ptr<FileHandle> openFile()
{
    return std::make_unique<FileHandle>();
}

int main()
{
    auto file{openFile()};

    // do whatever

    return 0;
}

In the above code, openFile() returns a std::unique_ptr by value. If this value isn't assigned to anything, the temporary return value will go out of scope and the FileHandle will be cleaned up. If it's assigned (as shown in main()), in C++14 or earlier, move semantics will be employed to transfer the FileHandle from the return value to the object assigned to (in the above example, file), and in C++17 or newer, the return will be elided. This makes returning a resource by std::unique_ptr much safer than returning raw pointers!

In general, you should not return std::unique_ptr by pointer (ever) or reference (unless you have a specific compelling reason to).

Passing std::unique_ptr to a function

If you want the function to take ownership of the contents of the pointer, pass the std::unique_ptr by value. Note that because copy semantics have been disabled, you'll need to use std::move to actually pass the variable in.

#include <iostream>
#include <memory> // for std::unique_ptr
#include <utility> // for std::move

class FileHandle
{
public:
    FileHandle() { std::cout << "File opened\n"; }
    ~FileHandle() { std::cout << "File closed\n"; }
};

std::ostream& operator<<(std::ostream& out, const FileHandle&)
{
    out << "I am a file handle";
    return out;
}

// This function takes ownership of the FileHandle, which isn't what we want
void takeOwnership(std::unique_ptr<FileHandle> file)
{
    if (file)
        std::cout << *file << '\n';
} // the FileHandle is destroyed here

int main()
{
    auto file{std::make_unique<FileHandle>()};

    // takeOwnership(file); // This doesn't work, need to use move semantics
    takeOwnership(std::move(file)); // ok: use move semantics

    std::cout << "Ending program\n";

    return 0;
}

The above program prints:

File opened
I am a file handle
File closed
Ending program

Note that in this case, ownership of the FileHandle was transferred to takeOwnership(), so the FileHandle was destroyed at the end of takeOwnership() rather than the end of main().

However, most of the time, you won't want the function to take ownership of the resource.

Although you can pass a std::unique_ptr by const reference (which will allow the function to use the object without assuming ownership), it's better to just pass the resource itself (by pointer or reference, depending on whether null is a valid argument). This allows the function to remain agnostic of how the caller is managing its resources.

To get a raw pointer from a std::unique_ptr, you can use the get() member function:

#include <memory> // for std::unique_ptr
#include <iostream>

class FileHandle
{
public:
    FileHandle() { std::cout << "File opened\n"; }
    ~FileHandle() { std::cout << "File closed\n"; }
};

std::ostream& operator<<(std::ostream& out, const FileHandle&)
{
    out << "I am a file handle";
    return out;
}

// The function only uses the resource, so we'll accept a pointer to the resource, not a reference to the whole std::unique_ptr<FileHandle>
void useFile(const FileHandle* file)
{
    if (file)
        std::cout << *file << '\n';
    else
        std::cout << "No file\n";
}

int main()
{
    auto file{std::make_unique<FileHandle>()};

    useFile(file.get()); // note: get() used here to get a pointer to the FileHandle

    std::cout << "Ending program\n";

    return 0;
} // The FileHandle is destroyed here

The above program prints:

File opened
I am a file handle
Ending program
File closed

std::unique_ptr and classes

You can, of course, use std::unique_ptr as a composition member of your class. This way, you don't have to worry about ensuring your class destructor deletes the dynamic memory, as the std::unique_ptr will be automatically destroyed when the class object is destroyed.

However, if the class object isn't destroyed properly (e.g., it's dynamically allocated and not deallocated properly), then the std::unique_ptr member won't be destroyed either, and the object being managed by the std::unique_ptr won't be deallocated.

Misusing std::unique_ptr

There are two easy ways to misuse std::unique_ptrs, both easily avoided. First, don't let multiple objects manage the same resource. For example:

FileHandle* handle{new FileHandle()};
std::unique_ptr<FileHandle> file1{handle};
std::unique_ptr<FileHandle> file2{handle};

While this is legal syntactically, the end result will be that both file1 and file2 will try to delete the FileHandle, which will lead to undefined behavior.

Second, don't manually delete the resource out from underneath the std::unique_ptr.

FileHandle* handle{new FileHandle()};
std::unique_ptr<FileHandle> file1{handle};
delete handle;

If you do, the std::unique_ptr will try to delete an already deleted resource, again leading to undefined behavior.

Note that std::make_unique() prevents both of the above cases from happening inadvertently.

Summary

std::unique_ptr purpose: The C++11 replacement for std::auto_ptr, designed to manage dynamically allocated objects with exclusive ownership. Lives in the header.

Move semantics: Unlike std::auto_ptr, std::unique_ptr properly implements move semantics. Copy operations are disabled; ownership transfer requires std::move().

Accessing managed objects: Use operator* to get a reference to the managed object, or operator-> to access members. Check if the std::unique_ptr is managing an object using its implicit cast to bool.

Array support: std::unique_ptr correctly distinguishes between scalar and array delete, making it safe for both. However, prefer std::array, std::vector, or std::string over smart pointers managing arrays.

std::make_unique(): Introduced in C++14, this function constructs objects and returns a std::unique_ptr managing them. Simpler to use, safer (prevents exception issues), and works well with auto type deduction.

Exception safety: std::make_unique() resolves potential memory leaks from unspecified function argument evaluation order when exceptions are thrown. This was fixed in C++17 for manual construction.

Returning from functions: std::unique_ptr can be safely returned by value. Move semantics (pre-C++17) or copy elision (C++17+) transfer ownership efficiently without copies.

Passing to functions: Pass by value to transfer ownership (requires std::move()). More commonly, pass the raw resource using get() to avoid transferring ownership while allowing the function to use the object.

Class composition: std::unique_ptr works well as a class member, automatically handling cleanup when the containing object is destroyed. However, if the class itself isn't properly destroyed, the std::unique_ptr won't be either.

Common mistakes: Never create multiple std::unique_ptr instances managing the same resource, and never manually delete the resource managed by a std::unique_ptr. Both cause undefined behavior.

Best Practice
Use std::unique_ptr for exclusive ownership of dynamically allocated objects. Prefer std::make_unique() over manual construction. Pass the raw pointer (via get()) to functions that don't need ownership.