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 | |