Smart Pointers in Modern C++

2018/9/14 posted in  C++ comments

C++11: auto_ptr, unique_ptr, shared_ptr, weak_ptr

Smart pointers can do virtually everything raw pointers can, but with far fewer opportunities for error.

unique_ptr

Use unique_ptr when you want to have single ownership (exclusive) of resource. When that unique_ptr is destructed, the resource is automatically released.

custom deleter

auto deleter = [](A* a){ ... delete a;} //lambda
unique_ptr<A, decltype(deleter)> A(new A, deleter)

move semantics

Unlike auto_ptr, copy assignment is not allowed for unique_ptr. Ownership is truly unique and safe. unique_ptr can be moved using the new move semantics:

unique_ptr<A> ptr1 (new A);
unique_ptr<A> ptr2 = std::move(ptr1);

shared_ptr

A shared_ptr is a container for a raw pointer. It is a reference counting ownership model i.e. it maintains the reference count of its contained pointer in cooperation with all copies of the shared_ptr. So, the counter is incremented each time a new shared_ptr points to the resource and decremented when the shared_ptr is destructed. An object will not be destroyed until all copies of shared_ptr have been destructed. So, we should use shared_ptr when we want to assign one raw pointer to multiple owners.

enable_shared_from_this

It enables you to get a valid shared_ptr instance to this. Without it, you would have no way of getting a shared_ptr to this, unless you already had one shared_ptr as a member.

class A: public enable_shared_from_this<A>
{
public:
    shared_ptr<A> getMe() { return shared_from_this(); }
}

The method getMe() returns a valid shared_ptr. Note that you cannot simply do shared_ptr<A> getMe() { return shared_ptr<A>(this); }. Because the returned shared_ptr will have a different reference counting control, which will end up holding a dangling reference when the object is deleted.

weak_ptr

A weak_ptr is created as a copy of shared_ptr. It provides access to an object that is owned by one or more shared_ptr, but does not participate in reference counting. The existence or destruction of weak_ptr has no effect on the shared_ptr or its other copies. It is required in some cases to break circular references between shared_ptr instances.

circular dependency of shared_ptr

Consider a scenario where we have two objects A and B. A has shared_ptr<B> b and B has shared_ptr<A> a. Hence, use_count will never reach zero and they will never get deleted.

basic uses of weak_ptr

// created as a copy of shared_ptr
std::weak_ptr<Widget> w_ptr(s_ptr);

// check if w_ptr is valid
if (!w_ptr.expired()) {
    // do something
}

// lock() creates a new shared_ptr that shares
// ownership of the managed object.
// If there is no managed object,
// the returned shared_ptr is also empty.
// i.e. expired() ? shared_ptr<T>() : shared_ptr<T>(*this)
if (auto s_ptr_2 = w_ptr.lock()) {
    // do something
} else {
    // do something
}

make_unique and make_shared

potential memory leak

processWidget(shared_ptr<Widget>(new Widget), getSomething());

It depends on compiler, if it firstly calls new Widget, then getSomething() and getSomething() throws an exception, Widget is leaked.

processWidget(make_shared<Widget>(), getSomething());

The correct way is as above.