Modern C++ - Smart pointers
Smart pointers: ensure that programs are free of memory and resource leaks and are exception-safe.
Header file: <memory>
unique_ptr
: Allows exactly one owner of the underlying pointer. Cannot be copied.shared_ptr
: Reference-counted smart pointer. The raw pointer is not deleted until all owners have gone out of scope or have otherwise given up ownership.weak_ptr
: provides access to an object that is owned by one or moreshared_ptr
instances, but does not participate in reference counting. Useful to break circular reference.
Smart pointers are crucial to the RAII (Resource Acquisition Is Initialization) programming idiom. RAII ensures that resource acquisition occurs at the same time that the object is initialized, so that all resources for the object are created and made ready in one line of code.
std::unique_ptr<A> p(new A());
NOTE: Do not use the new
or malloc
expression on the smart pointer itself.
Different from Java/C#: no separate garbage collector runs in the background; memory is managed through the standard C++ scoping rules so that the runtime environment is faster and more efficient.
std::make_unique
is the new new
(since C++14)
When creating a new object to store in a unique_ptr
, use std::make_unique
.
std::unique_ptr<Foo> v = std::make_unique<Foo>();
// or
auto v = std::make_unique<Foo>();
The only exception to this rule is when implementing a factory function that uses a protected
or private
constructor. In this case, you have to use new, but you should immediately wrap the result using absl::WrapUnique
.
When to use std::unique_ptr
?
If it is never std::move
d to or from another std::unique_ptr
, it likely should NOT be a std::unique_ptr
.
std::unique_ptr
conveys transferrable ownership which is unhelpful if ownership isn't being transferred.
Smart Pointers and the raw pointers
The stored pointer points to the object managed by the unique_ptr
, if any, or to nullptr
if the unique_ptr
is empty.
std::unique_ptr::get
does not make unique_ptr
release ownership of the pointer (i.e., it is still responsible for deleting the managed data at some point). Therefore, the value returned by this function shall not be used to construct a new managed pointer.
In order to obtain the stored pointer and release ownership over it, call std::unique_ptr::release
instead.
Move
A call to std::move
isn’t actually a move itself, it’s just a cast to an rvalue-reference. It’s only the use of that reference by a move constructor or move assignment that does the work.
std::vector<int> foo = GetSomeInts();
std::move(foo); // Does nothing.
// Invokes std::vector<int>’s move-constructor.
std::vector<int> bar = std::move(foo);
Prefer to pass and return std::unique_ptr
values
Prefer to pass and return unique_ptr
values, NOT pointers or references to unique_ptr
For example, use std::unique_ptr<Foo>
, NOT std::unique_ptr<Foo>&
or std::unique_ptr<Foo>*
.
// unique_ptr as the return value
std::unique_ptr<Foo> GetFoo();
// unique_ptr as an argument
void Bar(std::unique_ptr<Foo> arg);
std::unique_ptr
may make the class non-copyable
Having a std::unique_ptr
member variable makes this class non-copyable.
Even without explicitly deleting the copy constructor and copy assignment operator. Because std::unique_ptr
is not copyable.
When to std::move
a std::shared_ptr
?
Copying a std::shared_ptr
invokes atomic reference count increase and decrease.
Moving a std::shared_ptr
is cheap, since it avoids changing the atomic counters.
Also std::move
shows the intent of transferring the ownership, while a copy adds an additional owner.