Many of the following are excerpts from Scott Meyers - Effective Modern C++
Compare unique_ptr & shared_ptr
Comparison | unique_ptr | shared_ptr |
---|---|---|
Custom deleter | Encoded in the type. unique_ptr<Widget, decltype(deleteFunc)> w(new Widget, deleteFunc) (Implication)Smaller runtime data structures and faster runtime code, but the pointed-to types must be complete when compiler-generated special functions |
Not in the typeshared_ptr<Widget> w(new Widget, deleteFunc) (Implication)Larger runtime data structures and slower code, but pointed-to types need not be complete when compiler-generated special functions are employed |
Copy | Cannot be copied | Can be copied |
Conversion | Can convert to shared_ptr | Cannot convert to unique_ptr |
Size | Size will be increased by the customer deleter | Twice the size of a raw pointer: a raw pointer to the resource, a raw pointer to the resource’s reference count |
Control Black | None | Control block contains reference count, weak count, custom deleter, allocator etc |
std::enable_shared_from_this
If an object has two control blocks, it will lead to undefined behavior. Since constructing a shared_pointer from a raw pointer will create a control block, if we accidently create two shared_pointers that point to the same object, they will have different reference counts and will have undefined destruction logic… This will be even worse when we are doing that obliviously, for example:
1 |
|
This can be fixed by inheriting from a template: std::enable_shared_from_this<T>
:
1 |
|
weak_ptr
What is a weak pointer? It is a smart pointer that does not increase the reference count of the object and thus does not share ownership with the ponited-to resource, it usually points to an object that a shared_ptr points to.
Why do we need a weak pointer? It is useful when we want to detect when the pointer will be dangled, or when we want to break a cycle of reference between two objects.
How do we convert from a weak pointer to a shared pointer?
1 |
|
make constructor
Let’s name the way to use make_unique
and make_shared
to create pointer “method A”, the way to use unique_ptr, shared_ptr + new operator to construct the pointer “method B”.
It is preferred to use method A, for the following reasons:
- Less typing.
auto ptr = make_unique<T>()
vsunique_ptr<T> ptr(new T)
; - More efficient. Method A allocates memory of object pointer + control block pointer at once, producing smaller static code, while unique_ptr allocates them separately;
- Exception safety. For example, in a function like this:
f(unique_ptr<T>(new T), throw_function())
, the compiler is allowed to call:new T
,throw_function()
,unique_ptr<T>
in order, which causes T to be leaked after an error is thrown inthrow_function()
. Usingmake_unique<T>()
avoids this. If you want to achieve exception safety in method B, make sure afternew
, assign that to a smart pointer immediately, such asshared_ptr<T> assignImmediately(new T)
.
Some rare but possible reasons to use method B:
- It enables customized deleter;
- Method A will use the parentheses to perfect forward the parameters, rather than braces. So when a class(such as in
vector
) has initializer_list parameters, method A will not use that ctor. You need to use thenew
method to use the brace initialization or use some workaround; - When object type is quite large and the time between destruction of the last shared_ptr and weak_ptr is huge, what happens is: shared_ptr is
deleted
, the object the shared pointer refers to get deleted, however the control block is alive until no weak_ptr is pointing to this object. This delay of freeing up space won’t happen in method B case because space for the pointer to object and control block is allocated separately.