Howard E. Hinnant
2011-04-15

unique_ptr

unique_ptr is the C++11 replacement for auto_ptr which is now deprecated. This document serves as a brief tutorial on unique_ptr.

Basic Examples

Almost anything you can do with auto_ptr, you can do with unique_ptr using the same syntax:

unique_ptr<int> factory(int i)
{
    return unique_ptr<int>(new int(i));
}

void client(unique_ptr<int> p)
{
    // Ownership transferred into client
}   // int* deleted here

void test()
{
    unique_ptr<int> p = factory(2);
    // *p == 2
    p.reset(new int(3));
    // *p == 3
    client(factory(4));
}

You can return unique_ptr from factory functions, and transfer ownership into sinks by passing by value. The familiar member functions from auto_ptr are available: operator*(), operator->(), get(), release(), reset().

So what is the difference between unique_ptr and auto_ptr?

Note in the example above that every time unique_ptr is copied, the source of the copy is an rvalue or temporary.

One can make a copy of an rvalue unique_ptr, but one can not make a copy of an lvalue unique_ptr. If you try, the compiler will issue a diagnostic:

unique_ptr<int> factory(int i)
{
    return unique_ptr<int>(new int(i));
}

void client(unique_ptr<int> p)
{
    // Ownership transferred into client
}   // int* deleted here

void test()
{
    unique_ptr<int> p = factory(2);
    // *p == 2
    p.reset(new int(3));
    // *p == 3
    client(p);  // error: call to deleted constructor of unique_ptr<int>
}

Above, when one tries to pass the unique_ptr p to client (which makes a copy), the compiler objects. Previously we passed the result of factory(4) to client. The compiler did not object because the result of factory(4) is an rvalue.

If you have an lvalue unique_ptr and you really want to "copy" it, then special syntax is required to indicate that you know that you're not really copying the unique_ptr:

unique_ptr<int> factory(int i)
{
    return unique_ptr<int>(new int(i));
}

void client(unique_ptr<int> p)
{
    // Ownership transferred into client
}   // int* deleted here

void test()
{
    unique_ptr<int> p = factory(2);
    // *p == 2
    p.reset(new int(3));
    // *p == 3
    client(move(p));  // ok now
    // p is "null" here
}

Why?

Ok, so the big difference between unique_ptr and auto_ptr is that you can say:

auto_ptr<int> p(new int(1));
auto_ptr<int> p2 = p;

But you can not say:

unique_ptr<int> p(new int(1));
unique_ptr<int> p2 = p;

The reason: Safety in generic code. One can not really make copies of either auto_ptr or unique_ptr. Consider:

template <class T>
void foo(T t)
{
    T copy_of_t = t;  // line 4
    assert(copy_of_t == t);
}

It is not unusual at all for generic code to look like foo above. The assert is probably not actually there, but the assumption that the assert would hold often is there ... implicitly. Indeed, a popular implementation of std::sort had exactly this logic in 1996, which is exactly what prompted the second auto_ptr redesign (which helped, but didn't completely fix the problem).

To fix the problem, unique_ptr disables copying from lvalues (named objects). Thus, in generic code, if something looks like a copy, then it is a copy! If the generic code is instantiated with unique_ptr (and tries to copy), then unlike auto_ptr, it will not compile. You get your errors at compile time, instead of at run time.

foo(auto_ptr<int>(new int(1)));   // compiles, asserts at run time
foo(unique_ptr<int>(new int(1))); // does not even compile (error at line 4)

If desired, generic code can generically move objects if the algorithm designer knows that he no longer requires the source to have the same value:

template <class T>
void foo(T t)
{
    T copy_of_t = move(t);
    // value of t is not defined here
}

Furthermore, even generic code can safely move from rvalues because once the operation is completed, one no longer has a reference to the rvalue to detect a difference:

template <class T>
void foo(int i)
{
    T t = factory(i);  // implicit move from result of factory
    // move is undetectable by foo because it no longer references that
    // result directly.  foo now has a "copy" of that result named t.
}

foo<auto_ptr<int> >(1);    // ok
foo<unique_ptr<int> >(2);  // also ok

Why is unique_ptr this way? Because unique_ptr turns common run time errors associated with auto_ptr into compile time errors, especially when instantiating generic code (compile time errors are always better than run time errors).

Anything Else?

The most important difference between unique_ptr and auto_ptr is the refusal to copy from lvalues as described in the previous section. However unique_ptr has several more features which make it much more powerful and flexible than auto_ptr:

Custom Deleter

Much like you can supply an allocator to std::vector, you can supply a deleter to unique_ptr to customize how the resource is disposed of:

struct close_stream
{
    void operator()(std::ofstream* os) const
    {
        os->close();
    }
};

std::ofstream log_file;

typedef unique_ptr<std::ofstream, close_stream> FilePtr;

FilePtr get_log()
{
    log_file.open("log file");
    return FilePtr(&log_file);
}

void test()
{
    FilePtr fp = get_log();
    *fp << "some text\n";
}   // fp->close()

In the above example the resource isn't a heap object. The resource is the open state of a logging file. unique_ptr is modified by the client-supplied close_stream to cause ~unique_ptr() to close the stream.

Below is a unique_ptr that works with malloc and free instead of new and delete:

void test()
{
    unique_ptr<int, void (*)(void*)> p((int*)std::malloc(sizeof(int)), std::free);
    *p = 1;
}   // free(p.get())

As the above examples show, the deleter can be a functor or function pointer which takes a pointer to the owned resource. An instance of the deleter can be passed into the unique_ptr constructor. Indeed this is required if the deleter is a function pointer, else the deleter would be a null function pointer. To prevent this accident, the following will not compile. If the deleter is a function pointer, then one must supply a function pointer in the unique_ptr constructor:

void test()
{
    unique_ptr<int, void (*)(void*)> p((int*)std::malloc(sizeof(int)));
    *p = 1;
}   // free(p.get())

error: static_assert failed "unique_ptr constructed with null function pointer deleter"

The deleter can also be a reference to a deleter. In this case, again an lvalue deleter must be supplied in the unique_ptr constructor. This feature can be handy when dealing with a stateful deleter which you do not want to make a copy of:

class MyDeleter
{
    std::ofstream file_;
public:
    MyDeleter() : file_("filename") {}

    template <class T>
    void operator()(T* t)
    {
        file_ << "deleting " << t << '\n';
        delete t;
    }
};

MyDeleter my_deleter;

void test()
{
    unique_ptr<int, MyDeleter&> p1(new int(1), my_deleter);
    unique_ptr<int, MyDeleter&> p2(new int(2), my_deleter);
    // ...
}   // p2 freed and logged, p1 freed and logged

Custom Storage Type

To support placing unique_ptr into shared memory, the custom deleter can contain a custom pointer type (typically not a real pointer in shared memory applications). One simply places a nested type called pointer which emulates pointer behavior within your deleter, publicly accessible:

template <class T>
class MyDeleter
{
public:
    class pointer
    {
     public:
        friend bool operator==(pointer x, pointer y);
        friend bool operator!=(pointer x, pointer y);
       // ...
    };

    void operator()(pointer p);
};

void test()
{
    unique_ptr<int, MyDeleter<int> > p;
    MyDeleter<int>::pointer p2 = p.get();  // A custom pointer type used for storage
}

Support For void

Yes, you can say unique_ptr<void, CustomDeleter>. Just don't try to use the dereference operator! And you must use your own deleter. default_delete (the default deleter) requires a complete type to delete.

Safe Support For incomplete types

You can safely use unique_ptr<A> to implement the pimpl pattern as long as A is a complete type wherever ~unique_ptr<A> is instantiated (such as in the .cpp file). If you make a mistake, and try to delete A where it is an incomplete type, the default deleter (named default_delete<A>) will cause a compile time diagnostic:

struct A;

class B
{
    unique_ptr<A> ptr_;
public:
    B();
    ~B();
    // ...
    
    A& get() {return *ptr_;}
};

void test()
{
    B b;
    A& a = b.get();
}  // ok as long as B's special members are defined with a complete A

If (for example) ~B() is defaulted, then a static_assert can be expected because unique_ptr will attempt to delete an incomplete A.

Safe Support For Arrays

void test()
{
    unique_ptr<int[]> p(new int[3]);
    p[0] = 0;
    p[1] = 1;
    p[2] = 2;
}   // delete [] p.get() called

By including the [] after the type in the unique_ptr template parameter one indicates that one wishes the "array form" of unique_ptr. This form does not have dereference, member selection, or implicit derived to base conversions. But it does have an indexing operator which the non-array form lacks. And the correct form of delete will be called. You can also supply a custom deleter for this form also, and then the correct action to be taken is up to you.

const unique_ptr

A const unique_ptr may be considered a better scoped_ptr than even boost::scoped_ptr. The only way to transfer ownership away from a const unique_ptr is by using const_cast. Unlike scoped_ptr, you can't even swap const unique_ptr's. The overhead is the same (if using the default_delete or an empty custom deleter -- sizeof(unique_ptr<T>) == sizeof(scoped_ptr<T>)). And custom deleters are not even a possibility with scoped_ptr.

void test()
{
    const unique_ptr<int[]> p(new int[3]);
    // ownership is not going to be transferred away from p because it is const
    p[0] = 0;
    p[1] = 1;
    p[2] = 2;
}   // delete [] p.get() called

Synopsis

template <class T, class D = default_delete<T>>
class unique_ptr
{
public:
    typedef see below pointer;
    typedef T element_type;
    typedef D deleter_type;

    // constructors
    constexpr unique_ptr() noexcept;
    constexpr unique_ptr(nullptr_t) noexcept;
    explicit unique_ptr(pointer p) noexcept;
    unique_ptr(pointer p, see below d1) noexcept;
    unique_ptr(pointer p, see below d2) noexcept;
    unique_ptr(unique_ptr&& u) noexcept;
    template <class U, class E>
        unique_ptr(unique_ptr<U, E>&& u) noexcept;
    template <class U>
        unique_ptr(auto_ptr<U>&& u) noexcept;

    // destructor
    ~unique_ptr();

    // assignment
    unique_ptr& operator=(unique_ptr&& u) noexcept;
    template <class U, class E>
        unique_ptr& operator=(unique_ptr<U, E>&& u) noexcept;
    unique_ptr& operator=(nullptr_t) noexcept;

    // observers
    typename add_lvalue_reference<T>::type operator*() const;
    pointer operator->() const noexcept;
    pointer get() const noexcept;
    deleter_type& get_deleter() noexcept;
    const deleter_type& get_deleter() const noexcept;
    explicit operator bool() const noexcept;

    // modifiers
    pointer release() noexcept;
    void reset(pointer p = pointer()) noexcept;
    void swap(unique_ptr& u) noexcept;

    // disable copy from lvalue
    unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;
};

template <class T, class D>
class unique_ptr<T[], D>
{
public:
    typedef see below pointer;
    typedef T element_type;
    typedef D deleter_type;

    // constructors
    constexpr unique_ptr() noexcept;
    constexpr unique_ptr(nullptr_t) noexcept;
    explicit unique_ptr(pointer p) noexcept;
    unique_ptr(pointer p, see below d1) noexcept;
    unique_ptr(pointer p, see below d2) noexcept;
    unique_ptr(unique_ptr&& u) noexcept;

    // destructor
    ~unique_ptr();

    // assignment
    unique_ptr& operator=(unique_ptr&& u) noexcept;
    unique_ptr& operator=(nullptr_t) noexcept;

    // observers
    T& operator[](size_t i) const;
    pointer get() const noexcept;
    deleter_type& get_deleter() noexcept;
    const deleter_type& get_deleter() const noexcept;
    explicit operator bool() const noexcept;

    // modifiers
    pointer release() noexcept;
    void reset(pointer p = pointer()) noexcept;
    void reset(nullptr_t) noexcept;
    template <class U> void reset(U) = delete;
    void swap(unique_ptr& u) noexcept;

    // disable copy from lvalue
    unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;
};

template<class T, class D>
    void swap(unique_ptr<T, D>& x, unique_ptr<T, D>& y) noexcept;

template<class T1, class D1, class T2, class D2>
    bool operator==(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
template<class T1, class D1, class T2, class D2>
    bool operator!=(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);

template<class T1, class D1, class T2, class D2>
    bool operator<(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
template<class T1, class D1, class T2, class D2>
    bool operator<=(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
template<class T1, class D1, class T2, class D2>
    bool operator>(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
template<class T1, class D1, class T2, class D2>
    bool operator>=(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);

template <class T, class D>
    bool operator==(const unique_ptr<T, D>& x, nullptr_t) noexcept;
template <class T, class D>
    bool operator==(nullptr_t, const unique_ptr<T, D>& y) noexcept;
template <class T, class D>
    bool operator!=(const unique_ptr<T, D>& x, nullptr_t) noexcept;
template <class T, class D>
    bool operator!=(nullptr_t, const unique_ptr<T, D>& y) noexcept;
template <class T, class D>
    bool operator<(const unique_ptr<T, D>& x, nullptr_t);
template <class T, class D>
    bool operator<(nullptr_t, const unique_ptr<T, D>& y);
template <class T, class D>
    bool operator<=(const unique_ptr<T, D>& x, nullptr_t);
template <class T, class D>
    bool operator<=(nullptr_t, const unique_ptr<T, D>& y);
template <class T, class D>
    bool operator>(const unique_ptr<T, D>& x, nullptr_t);
template <class T, class D>
    bool operator>(nullptr_t, const unique_ptr<T, D>& y);
template <class T, class D>
    bool operator>=(const unique_ptr<T, D>& x, nullptr_t);
template <class T, class D>
    bool operator>=(nullptr_t, const unique_ptr<T, D>& y);