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 pointerp
.get()
-- Get the raw pointer stored in the unique pointer.reset(T* p)
-- Delete the pointee and reset top
.reset()
-- Delete the pointee and reset tonullptr
.release()
-- Return the raw pointer and set the unique pointer tonullptr
. 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! :-)