std::shared_ptr::unique()
的技术问题是在C ++ 17中弃用的原因是什么?
根据cppreference.com,{+ 1}}在C ++ 17中被弃用为
从C ++ 17开始,不推荐使用此函数,因为
std::shared_ptr::unique()
只是多线程环境中的近似值。
我理解这对于use_count
是正确的:虽然我持有对它的引用,但其他人可能会同时放弃他或创建新副本。 击>
但是如果use_count() > 1
返回1(这是我在调用use_count()
时感兴趣的话)那么就没有其他线程可以以一种活泼的方式改变这个值,所以我会期望这应该是安全的:
unique()
击> <击> 撞击>
我找到了这个文档:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0521r0.html建议弃用以回应 C ++ 17 CD评论CA 14 ,但我自己找不到评论。
作为替代方案,该文件建议增加一些注释,包括以下内容:
注意:当多个线程影响
if (myPtr && myPtr.unique()) { //Modify *myPtr }
的返回值时,结果应视为近似值。 特别是,use_count()
并不意味着通过以前被破坏的use_count() == 1
的访问在任何意义上都已完成。 - 结束注释
我理解这可能是shared_ptr
当前指定的方式(由于缺乏保证的同步),但为什么解决方案不仅仅是指定这样的同步,因此使上述模式安全?如果存在不允许这种同步的基本限制(或使其成本高昂),那么如何正确实现析构函数呢?
我忽略了@ alexeykuzmin0和@rubenvb提出的明显案例,因为到目前为止我只在use_count()
的实例上使用了unique()
其他线程本身无法访问的实例。因此,没有任何危险,特定的实例将以一种活泼的方式被复制。
我仍然有兴趣了解CA 14究竟是什么,因为我相信shared_ptr
的所有用例只要能保证与不同的{{1}发生的事情同步就会起作用其他线程上的实例。所以它对我来说似乎仍然是一个有用的工具,但我可能会忽略一些基本的东西。
为了说明我的想法,请考虑以下事项:
unique()
它是否有任何问题(如果shared_ptr
实际上与其他副本的析构函数同步)?
答案 0 :(得分:12)
请考虑以下代码:
// 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
。
答案 1 :(得分:7)
我认为P0521R0通过滥用shared_ptr
作为线程间同步来解决潜在的数据竞争。
它说use_count()
返回不可靠的引用计数值,因此,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()
没有任何同步效果,并且从[D] shared_ptr
的析构函数到[U]调用之前没有发生在之前的关系unique()
。
所以我们不能指望关系[W]⇒[D]⇒[U]⇒[R]和[W]⇒[R]。 ('⇒'表示发生在关系之前)。
已编辑:我发现了两个相关的LWG问题; LWG2434. shared_ptr::use_count() is efficient,LWG2776. shared_ptr unique() and use_count()。这只是一个推测,但WG21委员会优先考虑C ++标准库的现有实现,因此他们在C ++ 1z中编写了它的行为。
LWG2434引用(强调我的):
shared_ptr
和weak_ptr
注意到他们的use_count()
可能效率低下。 这是尝试确认重新链接的实现(例如,可以由Loki智能指针使用)。 但是,没有任何shared_ptr
实现使用重新链接,尤其是在C ++ 11认识到存在多线程之后。每个人都使用原子引用,因此use_count()
只是一个原子加载。
LWG2776引用(强调我的):
LWG 2434删除了
use_count()
和unique()
shared_ptr
中的“仅调试”限制,引入了一个错误。为了使unique()
生成有用且可靠的值,它需要一个synchronize子句,以确保通过另一个引用的先前访问对unique()
的成功调用者可见。 许多当前的实现使用宽松的负载,并且不提供此保证,因为标准中没有说明。对于调试/提示使用,这是正常的。没有它,规范不清楚,可能会产生误导。[...]
我更愿意将
use_count()
指定为仅提供实际计数的不可靠提示(另一种说明仅调试的方式)。或者像JF建议的那样弃用它。 我们无法在不增加更多击剑的情况下使use_count()
可靠。我们真的不希望有人等待use_count() == 2
确定另一个线程到那么远。不幸的是,我认为我们目前没有说什么来表明这是一个错误。这意味着
use_count()
通常使用memory_order_relaxed
,并且use_count()
并未指定或实施唯一。
答案 2 :(得分:3)
为了您的观赏乐趣:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0488r0.pdf
本文件包含所有NB(国家机构)对Issaquah会议的评论。 CA 14读到:
删除use_count()和的“仅调试”限制 shared_ptr中的unique()引入了一个bug:为了unique()来 产生一个有用且可靠的值,它需要一个synchronize子句 确保通过其他参考的先前访问可见 unique()的成功调用者。许多当前的实现使用a 放松的负荷,并没有提供这种保证,因为它没有说明 在标准中。对于调试/提示用法,这是正常的。没有它了 规范不明确且具有误导性。
答案 3 :(得分:-1)
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 中删除的原因:误导。