If you find that you need to share data or utilize reference counting, you should use a std::shared_ptr . If you need to reference shared data but don't want to contribute to the reference count, use a std::weak_ptr .
There are several reasons: Not all addresses are created equal; in particular, in non Von Neuman (e.g. Harvard) architectures pointers to code memory (where you often store constants) and a pointers to data memory are different.
Shared ownership:
The shared_ptr
and weak_ptr
the standard adopted are pretty much the same as their Boost counterparts. Use them when you need to share a resource and don't know which one will be the last to be alive. Use weak_ptr
to observe the shared resource without influencing its lifetime, not to break cycles. Cycles with shared_ptr
shouldn't normally happen - two resources can't own each other.
Note that Boost additionally offers shared_array
, which might be a suitable alternative to shared_ptr<std::vector<T> const>
.
Next, Boost offers intrusive_ptr
, which are a lightweight solution if your resource offers reference-counted management already and you want to adopt it to the RAII principle. This one was not adopted by the standard.
Unique ownership:
Boost also has a scoped_ptr
, which is not copyable and for which you can not specify a deleter. std::unique_ptr
is boost::scoped_ptr
on steroids and should be your default choice when you need a smart pointer. It allows you to specify a deleter in its template arguments and is movable, unlike boost::scoped_ptr
. It is also fully usable in STL containers as long as you don't use operations that need copyable types (obviously).
Note again, that Boost has an array version: scoped_array
, which the standard unified by requiring std::unique_ptr<T[]>
partial specialization that will delete[]
the pointer instead of delete
ing it (with the default_delete
r). std::unique_ptr<T[]>
also offers operator[]
instead of operator*
and operator->
.
Note that std::auto_ptr
is still in the standard, but it is deprecated.
§D.10 [depr.auto.ptr]
The class template
auto_ptr
is deprecated. [ Note: The class templateunique_ptr
(20.7.1) provides a better solution. —end note ]
No ownership:
Use dumb pointers (raw pointers) or references for non-owning references to resources and when you know that the resource will outlive the referencing object / scope. Prefer references and use raw pointers when you need either nullability or resettability.
If you want a non-owning reference to a resource, but you don't know if the resource will outlive the object that references it, pack the resource in a shared_ptr
and use a weak_ptr
- you can test if the parent shared_ptr
is alive with lock
, which will return a shared_ptr
that is non-null if the resource still exists. If want to test whether the resource is dead, use expired
. The two may sound similar, but are very different in the face of concurrent execution, as expired
only guarantees its return value for that single statement. A seemingly innocent test like
if(!wptr.expired())
something_assuming_the_resource_is_still_alive();
is a potential race condition.
Deciding what smart pointer to use is a question of ownership. When it comes to resource management, object A owns object B if it is in control of the lifetime of object B. For example, member variables are owned by their respective objects because the lifetime of member variables is tied to the lifetime of the object. You choose smart pointers based on how the object is owned.
Note that ownership in a software system is separate from ownership as we would think of it outside of software. For example, a person might "own" their home, but that doesn't necessarily mean that a Person
object has control over the lifetime of a House
object. Conflating these real world concepts with software concepts is a sure-fire way to program yourself into a hole.
If you have sole ownership of the object, use std::unique_ptr<T>
.
If you have shared ownership of the object...
- If there are no cycles in ownership, use std::shared_ptr<T>
.
- If there are cycles, define a "direction" and use std::shared_ptr<T>
in one direction and std::weak_ptr<T>
in the other.
If the object owns you, but there is potential of having no owner, use normal pointers T*
(e.g. parent pointers).
If the object owns you (or otherwise has guaranteed existence), use references T&
.
Caveat: Be aware of the costs of smart pointers. In memory or performance limited environments, it could be beneficial to just use normal pointers with a more manual scheme for managing memory.
The costs:
std::shared_ptr
has the overhead of a reference count increment on copy, plus a decrement on destruction followed by a 0-count check with deletion of the held object. Depending on the implementation, this can bloat your code and cause performance issues.Examples:
struct BinaryTree
{
Tree* m_parent;
std::unique_ptr<BinaryTree> m_children[2]; // or use std::array...
};
A binary tree does not own its parent, but the existence of a tree implies the existence of its parent (or nullptr
for root), so that uses a normal pointer. A binary tree (with value semantics) has sole ownership of its children, so those are std::unique_ptr
.
struct ListNode
{
std::shared_ptr<ListNode> m_next;
std::weak_ptr<ListNode> m_prev;
};
Here, the list node owns its next and previous lists, so we define a direction and use shared_ptr
for next and weak_ptr
for prev to break the cycle.
Use unique_ptr<T>
all the time except when you need reference counting, in which case use shared_ptr<T>
(and for very rare cases, weak_ptr<T>
to prevent reference cycles). In almost every case, transferrable unique ownership is just fine.
Raw pointers: Good only if you need covariant returns, non-owning pointing which can happen. They're not terrifically useful otherwise.
Array pointers: unique_ptr
has a specialization for T[]
which automatically calls delete[]
on the result, so you can safely do unique_ptr<int[]> p(new int[42]);
for example. shared_ptr
you'd still need a custom deleter, but you wouldn't need a specialized shared or unique array pointer. Of course, such things are usually best replaced by std::vector
anyway. Unfortunately shared_ptr
does not provide an array access function, so you'd still have to manually call get()
, but unique_ptr<T[]>
provides operator[]
instead of operator*
and operator->
. In any case, you have to bounds check yourself. This makes shared_ptr
slightly less user-friendly, although arguably the generic advantage and no Boost dependency makes unique_ptr
and shared_ptr
the winners again.
Scoped pointers: Made irrelevant by unique_ptr
, just like auto_ptr
.
There's really nothing more to it. In C++03 without move semantics this situation was very complicated, but in C++11 the advice is very simple.
There are still uses for other smart pointers, like intrusive_ptr
or interprocess_ptr
. However, they're very niche and completely unnecessary in the general case.
Cases of when to use unique_ptr
:
Cases of when to use shared_ptr
:
Cases of when to use weak_ptr
:
Feel free to edit and add more
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With