为什么不推荐使用 std::shared_ptr::unique()?
Why is std::shared_ptr::unique() deprecated?
std::shared_ptr::unique()
的技术问题是什么,这是它在 C++17 中弃用的原因?
根据 cppreference.com,std::shared_ptr::unique()
在 C++17 中被弃用为
this function is deprecated as of C++17 because use_count
is only an approximation in multi-threaded environment.
我理解这对 use_count() > 1
是正确的:当我保留对它的引用时,其他人可能会同时放弃他的引用或创建一个新副本。
但是如果 use_count()
returns 1(这是我在调用 unique()
时感兴趣的内容),那么就没有其他线程可以以活泼的方式更改该值,所以我希望这应该是安全的:
if (myPtr && myPtr.unique()) {
//Modify *myPtr
}
我自己搜索的结果:
我找到了这篇文档:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0521r0.html,它提议弃用以响应 C++17 CD 评论 CA 14,但我找不到该评论本身.
作为替代方案,该论文建议添加一些注释,包括以下内容:
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 destroyed shared_ptr
have in any sense completed. — end note
我知道这可能是当前指定方式 use_count()
的情况(由于缺乏保证同步),但为什么决议不只是指定这种同步并因此进行上述操作模式安全吗?如果存在不允许此类同步的基本限制(或使其成本高得惊人),那么如何正确实现析构函数?
更新:
我忽略了@alexeykuzmin0 和@rubenvb 提出的明显案例,因为到目前为止我只在其他线程本身无法访问的 shared_ptr
实例上使用了 unique()
。因此,不存在以不正当方式复制该特定实例的危险。
我仍然很想知道 CA 14 究竟是关于什么的,因为我相信我对 unique()
的所有用例只要保证与不同 shared_ptr
其他线程上的实例。所以它对我来说仍然是一个有用的工具,但我可能忽略了一些基本的东西。
为了说明我的想法,请考虑以下内容:
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;
};
它有什么问题吗(如果unique()
实际上会与其他副本的析构函数同步)?
考虑以下代码:
// global variable
std::shared_ptr<int> s = std::make_shared<int>();
// thread 1
if (s && s.unique()) {
// modify *s
}
// thread 2
auto s2 = s;
这里有一个经典的竞争条件:s2
可能(或可能不会)在线程 2 中创建为 s
的副本,而线程 1 在 if
中。
中的unique() == true
表示没有人有一个shared_ptr
指向同一个内存,但并不意味着任何其他线程无法直接或通过指针访问初始的shared_ptr
或参考资料。
为了您的观赏乐趣:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0488r0.pdf
本文档包含 NB(国家机构)对 Issaquah 会议的所有评论。 CA 14 读取:
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.
我认为 P0521R0 通过滥用 shared_ptr
作为线程间同步来解决 潜在的 数据竞争。
它说 use_count()
returns 不可靠的引用计数值,因此, unique()
成员函数在多线程时将无用。
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);
}
成员函数 unique()
没有任何同步效果,happens-before 关系来自 [D] shared_ptr
的析构函数到 [U] 呼叫 unique()
。
所以我们不能期待 [W] ⇒ [D] ⇒ [U] ⇒ [R] 和 [W] ⇒ [R] 的关系。 ('⇒' 表示 happens-before 关系)。
已编辑: 我发现了两个相关的 LWG 问题; LWG2434. shared_ptr::use_count() is efficient, LWG2776. shared_ptr unique() and use_count()。这只是一个推测,但 WG21 委员会优先考虑 C++ 标准库的现有实现,因此他们将其行为编入 C++1z。
LWG2434 引用(强调我的):
shared_ptr
and weak_ptr
have Notes that their use_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 any shared_ptr
implementations that use reflinking, especially after C++11 recognized the existence of multithreading. Everyone uses atomic refcounts, so use_count()
is just an atomic load.
LWG2776 引用(强调我的):
The removal of the "debug only" restriction for use_count()
and unique()
in shared_ptr
by LWG 2434 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 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 make use_count()
reliable without adding substantially more fencing. We really don't want someone waiting for use_count() == 2
to 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 uses memory_order_relaxed
, and unique is neither specified nor implemented in terms of use_count()
.
std::enable_shared_from_this
的存在就是制造麻烦的人
unique()
的有趣用法。实际上,std::enable_shared_from_this
允许从任何线程的原始指针创建一个新的 shared_ptr
。这意味着 unique()
永远不能保证任何事情。
但是考虑另一个库...虽然这与 shared_ptr
无关,但在 Qt 中,有一个 内部 方法调用 isDetached()
与(几乎) 与 unique()
相同的实现。
它用于一些非常有用的优化目的:当 true
时,指向的对象可以在不执行“写时复制”操作的情况下发生变化。
实际上,托管资源一旦唯一,就不能被源自另一个线程的操作共享。如果 enable_shared_from_this 不存在,shared_ptr 可能会出现相同的模式。
这就是恕我直言,unique()
已从 C++20 中删除的原因:误导。
std::shared_ptr::unique()
的技术问题是什么,这是它在 C++17 中弃用的原因?
根据 cppreference.com,std::shared_ptr::unique()
在 C++17 中被弃用为
this function is deprecated as of C++17 because
use_count
is only an approximation in multi-threaded environment.
我理解这对 use_count() > 1
是正确的:当我保留对它的引用时,其他人可能会同时放弃他的引用或创建一个新副本。
但是如果 use_count()
returns 1(这是我在调用 unique()
时感兴趣的内容),那么就没有其他线程可以以活泼的方式更改该值,所以我希望这应该是安全的:
if (myPtr && myPtr.unique()) {
//Modify *myPtr
}
我自己搜索的结果:
我找到了这篇文档:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0521r0.html,它提议弃用以响应 C++17 CD 评论 CA 14,但我找不到该评论本身.
作为替代方案,该论文建议添加一些注释,包括以下内容:
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
我知道这可能是当前指定方式 use_count()
的情况(由于缺乏保证同步),但为什么决议不只是指定这种同步并因此进行上述操作模式安全吗?如果存在不允许此类同步的基本限制(或使其成本高得惊人),那么如何正确实现析构函数?
更新:
我忽略了@alexeykuzmin0 和@rubenvb 提出的明显案例,因为到目前为止我只在其他线程本身无法访问的 shared_ptr
实例上使用了 unique()
。因此,不存在以不正当方式复制该特定实例的危险。
我仍然很想知道 CA 14 究竟是关于什么的,因为我相信我对 unique()
的所有用例只要保证与不同 shared_ptr
其他线程上的实例。所以它对我来说仍然是一个有用的工具,但我可能忽略了一些基本的东西。
为了说明我的想法,请考虑以下内容:
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;
};
它有什么问题吗(如果unique()
实际上会与其他副本的析构函数同步)?
考虑以下代码:
// global variable
std::shared_ptr<int> s = std::make_shared<int>();
// thread 1
if (s && s.unique()) {
// modify *s
}
// thread 2
auto s2 = s;
这里有一个经典的竞争条件:s2
可能(或可能不会)在线程 2 中创建为 s
的副本,而线程 1 在 if
中。
中的unique() == true
表示没有人有一个shared_ptr
指向同一个内存,但并不意味着任何其他线程无法直接或通过指针访问初始的shared_ptr
或参考资料。
为了您的观赏乐趣:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0488r0.pdf
本文档包含 NB(国家机构)对 Issaquah 会议的所有评论。 CA 14 读取:
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.
我认为 P0521R0 通过滥用 shared_ptr
作为线程间同步来解决 潜在的 数据竞争。
它说 use_count()
returns 不可靠的引用计数值,因此, unique()
成员函数在多线程时将无用。
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);
}
成员函数 unique()
没有任何同步效果,happens-before 关系来自 [D] shared_ptr
的析构函数到 [U] 呼叫 unique()
。
所以我们不能期待 [W] ⇒ [D] ⇒ [U] ⇒ [R] 和 [W] ⇒ [R] 的关系。 ('⇒' 表示 happens-before 关系)。
已编辑: 我发现了两个相关的 LWG 问题; LWG2434. shared_ptr::use_count() is efficient, LWG2776. shared_ptr unique() and use_count()。这只是一个推测,但 WG21 委员会优先考虑 C++ 标准库的现有实现,因此他们将其行为编入 C++1z。
LWG2434 引用(强调我的):
shared_ptr
andweak_ptr
have 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_ptr
implementations that use reflinking, especially after C++11 recognized the existence of multithreading. Everyone uses atomic refcounts, souse_count()
is just an atomic load.
LWG2776 引用(强调我的):
The removal of the "debug only" restriction for
use_count()
andunique()
inshared_ptr
by 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() == 2
to 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()
.
std::enable_shared_from_this
的存在就是制造麻烦的人
unique()
的有趣用法。实际上,std::enable_shared_from_this
允许从任何线程的原始指针创建一个新的 shared_ptr
。这意味着 unique()
永远不能保证任何事情。
但是考虑另一个库...虽然这与 shared_ptr
无关,但在 Qt 中,有一个 内部 方法调用 isDetached()
与(几乎) 与 unique()
相同的实现。
它用于一些非常有用的优化目的:当 true
时,指向的对象可以在不执行“写时复制”操作的情况下发生变化。
实际上,托管资源一旦唯一,就不能被源自另一个线程的操作共享。如果 enable_shared_from_this 不存在,shared_ptr 可能会出现相同的模式。
这就是恕我直言,unique()
已从 C++20 中删除的原因:误导。