Here we discuss about workings, performance and characteristics of std::shared_ptr and end the article with a test program which compares the performance of std::shared_ptr vs std::unique_ptr.
This article is inspired by Item 19 of Effective Modern C++ by Scott Meyers.
Just like the std::unique_ptr , std::shared_ptr is used to manage the lifetime of an underlying raw pointer, and delete it when it's no longer needed. However shared_ptr (going to drop the std prefix from now on) can be copied to a new shared_ptr ( sp_new = sp_orig) or copy constructed ( shared_ptr sp2 = sp1 , thus resulting in two or more shared_ptrs pointing to the same raw pointer. These shared_ptrs now share the ownership of the underlying pointer. They co-operate with each other ( by using reference count ) to decide when to finally delete the raw pointer.
These are some important features of std::shared_ptr -
Shared_ptr supports copy assignment, copy construction, move assignment, move construction. Moving the shared_ptr to a new shared_ptr, works like move on any other variable type.
For instance -
shared_ptr sp2 = std::move(sp1);
Here the state of sp1 is transferred to sp2, along with raw pointer, reference count, deleters etc ( collectively known as control block). and sp1 is set to null. Also note that in move construction, reference count is unchanged.
Just like std::unique_ptr, std::shared_ptr also supports custom deleters. However the difference is, deleters is not a part of the shared_ptr itself.
For example -
This enables different shared_ptrs with different deleters to be assigned (copy or move) to each other, and to co exist in a single collection like vector. We can also notice that deleter function with captured variables does not increase the size of the shared_ptr. This is because the captured variables is not part of shared_ptr, but is allocated in the free space within the control block.
Here are some rules for creation of control block -
It should also be noted that we cannot use make_shared when supplying custom deleter.
De-referencing a shared_ptr costs the same as de-referencing a raw pointer.
If we want to create shared_ptr from this object, then our class must inherit from enable_shared_from_this<OurClass>.
To create shared_ptr of this (current object), from any of the member function, we should call shared_from_this() . Keep in mind that before calling shared_from_this(), shared_ptr for this object must already be present. Hence to enforce this, such classes make constructors private, and have a factory kind of method which returns shared_ptr.
We can create a shared_ptr from unique_ptr, but not vice versa. We cannot create (convert) unique_ptr from shared_ptr even when reference count is 1.
Here is a program to compare the performance of unique_ptr VS shared_ptr
PS : Sean Parent on the C++ seasoning talk (Youtube) discourages using shared_ptr as it removes our ability to reason about the code locally, and makes the usage of shared_ptr almost similar to global variables.
This article is inspired by Item 19 of Effective Modern C++ by Scott Meyers.
Just like the std::unique_ptr , std::shared_ptr is used to manage the lifetime of an underlying raw pointer, and delete it when it's no longer needed. However shared_ptr (going to drop the std prefix from now on) can be copied to a new shared_ptr ( sp_new = sp_orig) or copy constructed ( shared_ptr sp2 = sp1 , thus resulting in two or more shared_ptrs pointing to the same raw pointer. These shared_ptrs now share the ownership of the underlying pointer. They co-operate with each other ( by using reference count ) to decide when to finally delete the raw pointer.
These are some important features of std::shared_ptr -
- They are twice the size of raw pointer (or unique_ptr), because we need another pointer pointing to the control block which consists of the reference count object.
- Increments and decrements on the reference count should be atomic, since shared_ptrs can be spread out in different threads. If two threads decrement the reference count, they should be ordered using atomic variables or mutex ( one after the other), otherwise we might lose one decrement.
Shared_ptr supports copy assignment, copy construction, move assignment, move construction. Moving the shared_ptr to a new shared_ptr, works like move on any other variable type.
For instance -
shared_ptr sp2 = std::move(sp1);
Here the state of sp1 is transferred to sp2, along with raw pointer, reference count, deleters etc ( collectively known as control block). and sp1 is set to null. Also note that in move construction, reference count is unchanged.
Just like std::unique_ptr, std::shared_ptr also supports custom deleters. However the difference is, deleters is not a part of the shared_ptr itself.
For example -
This enables different shared_ptrs with different deleters to be assigned (copy or move) to each other, and to co exist in a single collection like vector. We can also notice that deleter function with captured variables does not increase the size of the shared_ptr. This is because the captured variables is not part of shared_ptr, but is allocated in the free space within the control block.
Here are some rules for creation of control block -
- Creating shared_ptr using make_shared always creates a control block.
- It is created when a shared_ptr is constructed from unique_ptr. ( unique_ptr is then set to null).
- When shared_ptr is created using raw pointer.
It should also be noted that we cannot use make_shared when supplying custom deleter.
De-referencing a shared_ptr costs the same as de-referencing a raw pointer.
If we want to create shared_ptr from this object, then our class must inherit from enable_shared_from_this<OurClass>.
To create shared_ptr of this (current object), from any of the member function, we should call shared_from_this() . Keep in mind that before calling shared_from_this(), shared_ptr for this object must already be present. Hence to enforce this, such classes make constructors private, and have a factory kind of method which returns shared_ptr.
We can create a shared_ptr from unique_ptr, but not vice versa. We cannot create (convert) unique_ptr from shared_ptr even when reference count is 1.
Here is a program to compare the performance of unique_ptr VS shared_ptr
PS : Sean Parent on the C++ seasoning talk (Youtube) discourages using shared_ptr as it removes our ability to reason about the code locally, and makes the usage of shared_ptr almost similar to global variables.
Modern (Effective) C++ - std::shared_ptr
Reviewed by zeroingTheDot
on
February 03, 2018
Rating:
No comments: