C++11 Unique Pointer

C++11 introduced three new smart pointer class templates: std::unique_ptr, std::shared_ptr, and std::weak_ptr. These smart pointer class templates are designed to replace the old std::auto_ptr smart pointer, which is known to have some defect and deprecated now. In this post, I would like to give a brief introduction to std::unique_ptr.

The unique pointer is designed to be a fast smart pointer that has an exclusive ownership of the pointee. The unique pointer is responsible to delete the pointee whenever the unique pointer is reset or destructed.

Basic Usage

Here are the basic usages of unique pointer:

  • std::unique_ptr<T>() -- Default constructor which constructs an null pointer.
  • std::unique_ptr<T>(T* p) -- Construct an unique pointer from a the raw pointer p.
  • get() -- Get the raw pointer stored in the unique pointer.
  • reset(T* p) -- Delete the pointee and reset to p.
  • reset() -- Delete the pointee and reset to nullptr.
  • release() -- Return the raw pointer and set the unique pointer to nullptr. This function does not delete the pointee.

For example:

#include <iostream>
#include <memory>

class A {
public:
  A() {
    std::cout << "A::A(this=" << this << ")" << std::endl;
  }
  ~A() {
    std::cout << "A::~A(this=" << this << ")" << std::endl;
  }
  void test() {
    std::cout << "A::test()" << std::endl;
  }
};

int main() {
  std::cout << "TEST1: constructor and get()" << std::endl;
  {
    std::unique_ptr<A> p1;
    std::cout << "p1.get() = " << p1.get() << std::endl;

    std::unique_ptr<A> p2(new A());
    std::cout << "p2.get() = " << p2.get() << std::endl;

    // Call the member function
    p2->test();
  }

  std::cout << std::endl << "TEST2: reset()" << std::endl;
  {
    std::unique_ptr<A> p;
    std::cout << "p.get() = " << p.get() << std::endl;

    p.reset(new A());
    std::cout << "p.get() = " << p.get() << std::endl;

    p.reset(new A());
    std::cout << "p.get() = " << p.get() << std::endl;

    p.reset();
    std::cout << "p.get() = " << p.get() << std::endl;
  }

  std::cout << std::endl << "TEST3: release()" << std::endl;
  {
    A* a;

    {
      std::cout << "### BEGIN: unique pointer p" << std::endl;
      std::unique_ptr<A> p(new A());
      a = p.release();
    }
    std::cout << "### END: unique pointer p" << std::endl;

    delete a;
  }
}

Movable but Non-copyable

To ensure the exclusive ownership guarantee, the unique pointer is designed to be non-copyable. In the other words, you can't create an unique pointer by copying the other unique pointer instance. For example, the following code is illegal:

void example1() {
  std::unique_ptr<A> p1(new A());
  std::unique_ptr<A> p2(p1);  // WON'T COMPILE
}

This doesn't compile either:

void example2() {
  std::unique_ptr<A> p1(new A());
  std::unique_ptr<A> p2;
  p2 = p1;  // WON'T COMPILE
}

Fortunately, the unique pointer is movable, thus we can return an unique pointer without problems:

std::unique_ptr<A> create_instance() {
  std::unique_ptr<A> result(new A());
  // ...
  return result;  // OK
}

int main() {
  std::unique_ptr<A> pa(create_instance());
}

In addition, the unique pointer can be assigned from std::unique_ptr<T>&&, the R-value reference to the unique pointer:

bool build_object(std::unique_ptr<A> &result) {
  std::unique_ptr<A> tmp(new A());
  // ...
  result = std::move(tmp);  // OK
  return true;
}

Notice that in this example, the std::move() is essential. We have to cast the local variable tmp to std::unique_ptr<A>&& with std::move(tmp); otherwise, the code won't compile because tmp is an L-value.

Derived Class

std::unique_ptr<T> is similar to the raw pointer type T*. If class B is derived from class A, then we can upcast std::unique_ptr<B> to std::unique_ptr<A>. However, similar to the raw pointer, the late binding will only happen on the virtual functions. For the non-virtual functions, the callee function will be statically resolved at compile time. This rule also applies to the destructor of the pointee.

class A { /* ... Skipped ... */ };

class B : public A {
public:
  B() {
    std::cout << "B::B(this=" << this << ")" << std::endl;
  }
  ~B() {
    std::cout << "B::~B(this=" << this << ")" << std::endl;
  }
};

int main() {
  std::cout << "### TEST 1" << std::endl;
  {
    std::unique_ptr<A> pa(new B());
  }

  std::cout << std::endl;
  std::cout << "### TEST 2" << std::endl;
  {
    std::unique_ptr<B> pb(new B());
  }
}

The output shows that:

### TEST 1
A::A(this=0xa6d010)
B::B(this=0xa6d010)
A::~A(this=0xa6d010)

### TEST 2
A::A(this=0xa6d010)
B::B(this=0xa6d010)
B::~B(this=0xa6d010)
A::~A(this=0xa6d010)

Notice that the object allocated at address 0xa6d010 is not destructed with B::~B(). Conceptually, the main() function above is equivalent to:

int main() {
  std::cout << "### TEST 1" << std::endl;
  {
    A *pa = new B();
    delete pa;
  }

  std::cout << std::endl;
  std::cout << "### TEST 2" << std::endl;
  {
    B *pb = new B();
    delete pb;
  }
}

Since the destructor of A is not declared as virtual, the invocation of the destructor won't be dynamically dispatched. To fix the problem, we have to declare A::~A() as virtual.

Pointer to Array

The unique pointer can point to an array as well. However, it is important that the pointer type should be A[] instead of A. If the element type is incorrect, then the default deleter won't be able to delete the array properly.

// Correct Example
{
  std::unique_ptr<A[]> p(new A[3]);
}

std::cout << std::endl;

// Incorrect Example
{
  std::unique_ptr<A> p(new A[3]);
}

Here is the output of the code snippet above:

A::A(this=0x2161018)
A::A(this=0x2161019)
A::A(this=0x216101a)
A::~A(this=0x216101a)
A::~A(this=0x2161019)
A::~A(this=0x2161018)

A::A(this=0x2161018)
A::A(this=0x2161019)
A::A(this=0x216101a)
A::~A(this=0x216101a)

Notice that the second unique pointer will only delete the first element of the array since the unique pointer thought it was only pointing to one element.

Custom Deleter

In fact, the unique pointer class template std::unique_ptr<T, D> has two template parameters. The first template parameter is the pointee type, and the second template parameter is the deleter type. With the deleter type, we can customize the destruction policy of the pointee.

If the programmer doesn't specify the deleter type, then the default deleter will be std::default_delete. The default deleter will destruct the object with delete operator when T is a non-array type and destruct the object with delete[] operator when T is an array type.

In the following example, we are going to define a ExampleDeleter function object which will call the object pool to reclaim and recycle the pointee instead of deleting the pointee:

#include <cassert>
#include <iostream>
#include <list>
#include <memory>

class ExamplePool;

class Example {
private:
  ExamplePool *pool_;

public:
  Example(ExamplePool &pool) : pool_(&pool) {
    std::cout << "Example::Example(this=" << this << ")" << std::endl;
  }

  ~Example() {
    std::cout << "Example::~Example(this=" << this << ")" << std::endl;
  }

  inline void reclaim();
};

class ExampleDeleter {
public:
  void operator()(Example *ptr) {
    ptr->reclaim();
  }
};

class ExamplePool {
public:
  typedef std::unique_ptr<Example, ExampleDeleter> Handle;

private:
  std::list<Example> used_list_;
  std::list<Example> free_list_;

public:
  Handle create() {
    if (free_list_.empty()) {
      used_list_.emplace_back(*this);
    } else {
      std::cout << "Reuse: " << &used_list_.back() << std::endl;
      used_list_.splice(used_list_.end(), free_list_, free_list_.begin());
    }
    return Handle(&used_list_.back());
  }

  void reclaim(Example *ptr) {
    std::cout << "Reclaim: " << ptr << std::endl;
    for (auto it = used_list_.begin(), end = used_list_.end();
         it != end; ++it) {
      if (&*it == ptr) {
        free_list_.splice(free_list_.end(), used_list_, it);
        return;
      }
    }
    assert(0 && "ptr not found");
  }
};

inline void Example::reclaim() {
  pool_->reclaim(this);
}

int main() {
  ExamplePool pool;

  {
    std::cout << "### BEGIN: h1" << std::endl;
    auto h1 = pool.create();

    {
      std::cout << "### BEGIN: h2" << std::endl;
      auto h2 = pool.create();
    }
    std::cout << "### END: h2" << std::endl;

    {
      std::cout << "### BEGIN: h3" << std::endl;
      auto h3 = pool.create();
      std::cout << "### BEGIN: h4" << std::endl;
      auto h4 = pool.create();
    }
    std::cout << "### END: h3, h4" << std::endl;
  }
  std::cout << "### END: h1" << std::endl;

  {
    std::cout << "### BEGIN: h5" << std::endl;
    auto h5 = pool.create();
    {
      std::cout << "### BEGIN: h6" << std::endl;
      auto h6 = pool.create();
      std::cout << "### BEGIN: h7" << std::endl;
      auto h7 = pool.create();
      std::cout << "### BEGIN: h8" << std::endl;
      auto h8 = pool.create();
      std::cout << "### BEGIN: h9" << std::endl;
      auto h9 = pool.create();
    }
    std::cout << "### END: h6, h7, h8, h9" << std::endl;

    std::cout << "### BEGIN: h10" << std::endl;
    auto h10 = pool.create();
  }
  std::cout << "### END: h5, h10" << std::endl;

  return 0;
}

Linked List Example

Finally, I would like to demonstrate that can implement the singly linked list with the following code:

#include <iostream>
#include <memory>

class List {
private:
  std::unique_ptr<List> next_;

public:
  List() {
    std::cout << "List::List(this=" << this << ")" << std::endl;
  }

  ~List() {
    std::cout << "List::~List(this=" << this << ")" << std::endl;
  }

  List* next() {
    return next_.get();
  }

  void insert_after(std::unique_ptr<List> ins) {
    ins->next_ = std::move(next_);
    next_ = std::move(ins);
  }
};

int main() {
  std::cout << "---- BEGIN first" << std::endl;
  {
    std::unique_ptr<List> first(new List());
    std::cout << "---- BEGIN second" << std::endl;
    {
      std::unique_ptr<List> second(new List());
      first->insert_after(std::move(second));
    }
    std::cout << "---- END second" << std::endl;
  }
  std::cout << "---- END first" << std::endl;
}

Conclusion

In this post, I have given a brief introduction to std::unique_ptr. We started from the basic usage of unique pointer and then we went through different topics including the pointer to derived classes, the pointer to array, and the unique pointer deleter. Finally, we showed that we can build a simple singly linked list with std::unique_ptr. Hope you enjoy this article.

In the next post, I would like to introduce the usage of the shared smart pointers. Both std::shared_ptr and std::weak_ptr are included. Stay in tune! :-)