What is the technical problem with std::shared_ptr::unique()
that is the reason for its deprecation in C++17?
According to cppreference.com, std::shared_ptr::unique()
is deprecated in C++17 as
this function is deprecated as of C++17 because
use_count
is only an approximation in multi-threaded environment.
I understand this to be true for use_count() > 1
: While I'm holding a reference to it, someone else might simultaneously let go of his or create a new copy.
But if use_count()
returns 1 (which is what I'm interested in when calling unique()
) then there is no other thread that could change that value in a racy way, so I would expect that this should be safe:
if (myPtr && myPtr.unique()) { //Modify *myPtr }
I found this document: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0521r0.html which proposes the deprecation in response to C++17 CD comment CA 14, but I couldn't find said comment itself.
As an alternative, that paper proposed adding some notes including the following:
Note: When multiple threads can affect the return value of
use_count()
, the result should be treated as approximate. In particular,use_count() == 1
does not imply that accesses through a previously destroyedshared_ptr
have in any sense completed. — end note
I understand that this might be the case for the way use_count()
is currently specified (due to the lack of guaranteed synchronization), but why was the resolution not just to specify such synchronization and hence make the above pattern safe? If there was a fundamental limitation that wouldn't allow such synchronization (or make it forbiddingly costly), then how is it possible to correctly implement the destructor?
I overlooked the obvious case presented by @alexeykuzmin0 and @rubenvb, because so far I only used unique()
on instances of shared_ptr
that were not accessible to other threads themselves. So there was no danger that that particular instance would get copied in a racy way.
I still would be interested to hear what exactly CA 14 was about, because I believe that all my use cases for unique()
would work as long as it is guaranteed to synchronize with whatever happens to different shared_ptr
instances on other threads. So it still seems like a useful tool to me, but I might overlook something fundamental here.
To illustrate what I have in mind, consider the following:
class MemoryCache { public: MemoryCache(size_t size) : _cache(size) { for (auto& ptr : _cache) { ptr = std::make_shared<std::array<uint8_t, 256>>(); } } // the returned chunk of memory might be passed to a different thread(s), // but the function is never accessed from two threads at the same time std::shared_ptr<std::array<uint8_t,256>> getChunk() { auto it = std::find_if(_cache.begin(), _cache.end(), [](auto& ptr) { return ptr.unique(); }); if (it != _cache.end()) { //memory is no longer used by previous user, so it can be given to someone else return *it; } else { return{}; } } private: std::vector<std::shared_ptr<std::array<uint8_t, 256>>> _cache; };
Is there anything wrong with it (if unique()
would actually synchronize with the destructors of other copies)?
In short: Use unique_ptr when you want a single pointer to an object that will be reclaimed when that single pointer is destroyed. Use shared_ptr when you want multiple pointers to the same resource.
std::shared_ptr is not thread safe. A shared pointer is a pair of two pointers, one to the object and one to a control block (holding the ref counter, links to weak pointers ...).
So, we should use shared_ptr when we want to assign one raw pointer to multiple owners. // referring to the same managed object. When to use shared_ptr? Use shared_ptr if you want to share ownership of a resource.
The smart pointer has an internal counter which is decreased each time that a std::shared_ptr , pointing to the same resource, goes out of scope – this technique is called reference counting. When the last shared pointer is destroyed, the counter goes to zero, and the memory is deallocated.
Consider the following code:
// global variable std::shared_ptr<int> s = std::make_shared<int>(); // thread 1 if (s && s.unique()) { // modify *s } // thread 2 auto s2 = s;
Here we have a classic race condition: s2
may (or may not) be created as a copy of s
in thread 2 while thread 1 is inside the if
.
The unique() == true
means that no one has a shared_ptr
pointing to the same memory, but does not mean any other threads have no access to initial shared_ptr
directly or through pointers or references.
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