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::shar
I think that P0521R0 solves potentially data race by misusing shared_ptr as inter-thread synchronization.
It says use_count() returns unreliable refcount value, and so, unique() member function will be useless when multithreading.
int main() {
int result = 0;
auto sp1 = std::make_shared<int>(0); // refcount: 1
// Start another thread
std::thread another_thread([&result, sp2 = sp1]{ // refcount: 1 -> 2
result = 42; // [W] store to result
// [D] expire sp2 scope, and refcount: 2 -> 1
});
// Do multithreading stuff:
// Other threads may concurrently increment/decrement refcounf.
if (sp1.unique()) { // [U] refcount == 1?
assert(result == 42); // [R] read from result
// This [R] read action cause data race w.r.t [W] write action.
}
another_thread.join();
// Side note: thread termination and join() member function
// have happens-before relationship, so [W] happens-before [R]
// and there is no data race on following read action.
assert(result == 42);
}
The member function unique() does not have any synchronization effect and there're no happens-before relationship from [D] shared_ptr's destructor to [U] calling unique().
So we cannot expect relationship [W] ⇒ [D] ⇒ [U] ⇒ [R] and [W] ⇒ [R]. ('⇒' denotes happens-before relationship).
EDITED: I found two related LWG Issues; LWG2434. shared_ptr::use_count() is efficient, LWG2776. shared_ptr unique() and use_count(). It is just a speculation, but WG21 Committee gives priority to the existing implementation of C++ Standard Library, so they codify its behavior in C++1z.
LWG2434 quote (emphasis mine):
shared_ptrandweak_ptrhave Notes that theiruse_count()might be inefficient. This is an attempt to acknowledge reflinked implementations (which can be used by Loki smart pointers, for example). However, there aren't anyshared_ptrimplementations that use reflinking, especially after C++11 recognized the existence of multithreading. Everyone uses atomic refcounts, souse_count()is just an atomic load.
LWG2776 quote (emphasis mine):
The removal of the "debug only" restriction for
use_count()andunique()inshared_ptrby LWG 2434 introduced a bug. In order forunique()to produce a useful and reliable value, it needs a synchronize clause to ensure that prior accesses through another reference are visible to the successful caller ofunique(). Many current implementations use a relaxed load, and do not provide this guarantee, since it's not stated in the standard. For debug/hint usage that was OK. Without it the specification is unclear and probably misleading.[...]
I would prefer to specify
use_count()as only providing an unreliable hint of the actual count (another way of saying debug only). Or deprecate it, as JF suggested. We can't makeuse_count()reliable without adding substantially more fencing. We really don't want someone waiting foruse_count() == 2to determine that another thread got that far. And unfortunately, I don't think we currently say anything to make it clear that's a mistake.This would imply that
use_count()normally usesmemory_order_relaxed, and unique is neither specified nor implemented in terms ofuse_count().
For your viewing pleasure: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0488r0.pdf
This document contains all NB (National Body) comments for the Issaquah meeting. CA 14 reads:
The removal of the "debug only" restriction for use_count() and unique() in shared_ptr introduced a bug: in order for unique() to produce a useful and reliable value, it needs a synchronize clause to ensure that prior accesses through another reference are visible to the successful caller of unique(). Many current implementations use a relaxed load, and do not provide this guarantee, since it's not stated in the Standard. For debug/hint usage that was OK. Without it the specification is unclear and misleading.
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.