What is std::shared_ptr
The implementation of std::shared_ptr
shown in this page is from MSVC STL.
Architecture
classDiagram
_Ptr_base <|-- shared_ptr
_Ptr_base <|-- weak_ptr
_Ptr_base o-- _Ref_count_base
_Ref_count_base <|-- _Ref_count
_Ptr_base: - element_type* _ptr
_Ptr_base: - _Ref_count_base* _Rep
_Ptr_base: - _Move_construct_from(...)
_Ptr_base: - _Copy_construct_from(...)
_Ptr_base: - _Alias_construct_from(...)
_Ptr_base: - _Alias_move_construct_from(...)
_Ptr_base: - _Construct_from_weak(...)
_Ptr_base: - _Weakly_construct_from(...)
shared_ptr: + Constructor()
shared_ptr: + Assignment()
shared_ptr: + Destructor()
shared_ptr: + swap()
shared_ptr: + get()
shared_ptr: + reset()
shared_ptr: + operator*
shared_ptr: + operator->
shared_ptr: + operator[]
shared_ptr: + operator bool
weak_ptr: + Constructor()
weak_ptr: + Assignment()
weak_ptr: + Destructor()
weak_ptr: + swap()
weak_ptr: + reset()
weak_ptr: + expired()
weak_ptr: + lock()
_Ref_count_base: - _Atomic_counter_t _Uses
_Ref_count_base: - _Atomic_counter_t _Weaks
_Ref_count_base: - _Incref()
_Ref_count_base: - _Incwref()
_Ref_count_base: - _Decref()
_Ref_count_base: - _Decwref()
_Ref_count_base: - _Use_count()
_Ref_count_base: - _Destroy()
_Ref_count_base: - _Delete_this()
_Ref_count: - _Ty* _ptr
_Ref_count: - _Destroy()
_Ref_count: - _Delete_this()
Note that element_type* _ptr
in _Ptr_base
isn’t the same with _Ty* _ptr
in _Ref_count
strictly.
Control Block (_Ref_count_base
)
1 |
|
1 |
|
Ptr_base
1 |
|
shared_ptr
1 |
|
shared_ptr
and array
Before C++17, we have to specify deleter when using shared_ptr
for array. e.g.
1 |
|
After C++17, shared_ptr
supports array and we don’t have to provide custom deleter. e.g.
1 |
|
_Set_ptr_rep_and_enable_shared
_Set_ptr_rep_and_enable_shared
is related to enable_from_shared_this
.
_Wptr
is actually a weak_ptr
. expired()
means its use count is 0. In other words, it can be initialized.
1 |
|
enable_from_shared_this
1 |
|
Notice that there is no way to initialize the _Wptr
in enable_shared_from_this
hence we need to construct shared_ptr
in advance to apply enable_shared_from_this
correctly.
make_shared
We all know make_shared
is the best practice of the usage about shared_ptr
. But why?
_Ref_count_obj2
1 |
|
1 |
|
We can find the counters in _Ref_count_base
and the target object (_Storage
in _Ref_count_obj2
) are allocated at the same time.
There are at lease 2 advantages in this way:
- Allocates memory at a tight memory space.
- Allocates just once to reduce the number of system calls.
The implementation of make_shared
1 |
|
Deleter in shared_ptr
An interesting point:
classDiagram
shared_ptr o-- _Ref_count
shared_ptr: - element_type* _ptr
_Ref_count: - _Ty* _ptr
_Ref_count: - _Destroy()
_Ref_count: - _Delete_this()
There are 2 pointers:
element_type* _ptr
: the pointer of specified type forshared_ptr<T>
_Ty* _ptr
: the pointer approximating to the real type
Maybe it’s still a little confusing.
Given a base class and its derived class:
1 |
|
Note that their destructors aren’t marked as
virtual
.
1 |
|
The first two cases will result in memory leak, but the last one will release correctly. Why?
The reason why it’s special to bind an object with shared_ptr
is that shared_ptr
has _Ref_count
leveraging the real pointer and real deleter!
More specifically, in this case, element_type*
in shared_ptr<T>
refers to Base*
. But _Ref_count<_Ty>
is specialized as _Ref_count<Derived>
thus _Ty*
in _Ref_count
refers to Derived*
, and _Destroy()
is to delete a Derived
object.
However, the example below would still result in memory leak.
1 |
|
Because the original type of the object has lost when the shared_ptr
is constructed.
weak_ptr
1 |
|