What’s the difference between them?
Each of the smart pointers provides a variation of the ownership rules of the resource being manipulated. For example, an intelligent pointer that has ownership without sharing manipulates the resource in a way that no other intelligent pointer can have shared ownership of the same resource. Smart hands with shared ownership allow more than one smart pointer to handle the same feature at the same time.
Note: I will omit the advantages of a smart pointer over a primitive pointer, since this discussion does not fit this thread.
Come on the differences:
1. std::unique_ptr
: Sole possession
A Std::unique_ptr manages a resource through a pointer and discards that resource when the std::unique_ptr
falls out of scope (or when .reset()
is called.)
That one smart Pointer implements the rules of sole possession. That is, it is not possible to copy a std::unique_ptr
, thus ensuring that the resource remains unique to the std::unique_ptr
original. You can, however, transfer possession from that resource to another std::unique_ptr
, leaving the original smart Pointer empty (ie without a resource to manage.)
For example:
auto p1 = std::make_unique<int>(42); // `std::make_unique` é C++14.
//std::unique_ptr<int> p2 = p1; // Errado: não é possível copiar um `std::unique_ptr`.
std::unique_ptr<int> p3 = std::move(p1); // Ok: posse do recurso foi transferida.
In the above example, the variable p1
is a std::unique_ptr<int>
, that manages a pointer of int
for a dynamic memory.
In the second line (commented), try to copy a std::unique_ptr
is invalid and the compiler will complain (i.e. the code will not compile). This is purposeful: the copy-constructor of std::unique_ptr
is implicitly declared as deleted.
On the third line, instead of a copy, we have the transfer of possession of the pointer. By calling Std: with the variable p1
, we’re saying that " p1
can be moved, ie your resources can be transferred". This is important because it is now the move-constructor that will be called. It will copy the pointer managed by p1
and then cancel it. The following code demonstrates this effect:
assert(p3.get() != nullptr); // `p3` toma posse do recurso gerenciado por `p1`.
assert(*p3 == 42);
assert(p1.get() == nullptr); // `p1` agora não gerencia recurso algum.
2. std::shared_ptr
: Shared possession
A Std::shared_ptr also manages a resource through a pointer, except that instances of std::shared_ptr
can be copied. We call this shared ownership. This smart Pointer only discards the resource if it is the only one in possession of it.
When a copy of this smart Pointer happens, a reference counter (i.e. the number of smart pointers sharing the same resource) is incremented. When one of the smart pointers goes out of scope, or .reset()
is called in it, this counter is decremented, but the resource remains alive. The resource only dies when the count of shared possessions reaches zero.
For example:
{
auto s1 = std::make_shared<int>(42);
{
// Ok: a cópia significa compartilhamento do mesmo recurso. Contador incrementa.
std::shared_ptr<int> s2 = s1;
} // s2 sai de escopo e o contador decrementa.
} // s1 sai de escopo, contador decrementa e descarta o recurso.
A feature that std::shared_ptr
has to be able to share the same resource, but point to a totally different place. This is the aliasing constructor. It allows us to build a std::shared_ptr
who shares possession with one another std::shared_ptr
, but having a different pointer value:
struct S
{
int i;
float f;
S(int i, float f) : i(i), f(f) {}
};
auto s = std::make_shared<S>(42, 3.14f);
// Esses dois `shared_ptr`s seguintes compartilham o mesmo
// recurso do `s`, só que eles apontam pra lugares diferentes.
std::shared_ptr<int> si(s, &s->i);
std::shared_ptr<float> sf(s, &s->f);
// Ao de-referenciá-los, acessamos os membros do `s`.
assert(*si == 42);
assert(*sf == 3.14f);
As possession is still of the same resource, the reference counter works the same. That is, this S
will only be discarded when all std:shared_ptr
only to release the possession.
3. std::weak_ptr
: Weak shared ownership
We still have the smart pointer Std::weak_ptr. This one goes hand in hand with the std::shared_ptr
. A std::weak_ptr
is a smart Pointer that maintains a reference unoccupied of a resource managed by a std::shared_ptr
.
The idea of std::weak_ptr
is to implement a temporary possession of the resource of a std::shared_ptr
. He observes this resource without interfering in his life. To access it, this std::weak_ptr
is converted to a std::shared_ptr
to have a temporary possession of the resource. This is useful for when you want to access a resource only if it is available.
4. std::observer_ptr
: No possession (library Fundamentals TS v2)
The pointer donkey Std::observer_ptr holds a primitive pointer with no possession whatsoever of this pointer resource. It is only an observer and is not responsible for resource management.
std::observer_ptr
does not exist in any previous revision or in C++17. It is part of the proposal library Fundamentals TS v2 and maybe can be added in C++20.
How to know what kind of smart pointer to use?
When you have a feature and only want it to be automatically managed (i.e. there is no other non-trivial requirement and just want to have your lifespan automatically manipulated), std::unique_ptr
then it’s enough. Most cases fit here. And actually use new
and delete
in normal codes is discouraged, it is recommended to use std::unique_ptr
in place.
For features with special needs, such as features used across multiple threads, then std::shared_ptr
will do very well, since its main purpose is to make it safe to share resources between competing codes. Remember: std::shared_ptr
has a cost for this safety. Your reference counter is atomic1, making copies more expensive (slow). If you need to share ownership of a resource in a non-competitive or unparalleled code, I advise sticking to other smart pointers.
Already std::weak_ptr
is useful to prevent circular references between std::shared_ptr
s. There may be cases where a class Foo
may be in possession of a class Bar
, and at the same time Bar
have possession over Foo
. In this situation, one keeps the other alive, the counter never reaches zero and no one is discarded in the end. Then memory leakage happens, since the resources are not released. A std::weak_ptr
breaks this cycle, since you only have possession of the resource after accessing it (and this possession is temporary).
A pointer like std::observer_ptr
has no apparent use at first sight. However, its sole purpose is to be an alternative to primitive pointers that are used for the same purpose: just to observe a resource without having any ownership or responsibility over it. It is a vocabulary type and serves only to show the pointer intention without having to analyze the code in depth.
We have yet another smart pointer Std::atomic_shared_ptr being proposed for C++ (Concurrency TS). A std::atomic_shared_ptr
is just one std::shared_ptr
with a better interface than free functions to perform atomic operations with the asset in possession.
1 Depending on the implementation, the operation of incrementing/decreasing the counter is not atomic if the program does not use threads (i.e. does not compile with -pthread
). But don’t assume that this is the case for all implementations.