我想在我的代码中使用std::atomic<std::shared_ptr>
,以便可以对shared_ptr进行原子更新,但在访问shared_ptr时遇到问题。原子上的load()方法似乎减少了shared_ptr上的ref-count,因此如果没有被释放,我实际上无法使用该对象。
这是一段显示问题的简化代码......
typedef shared_ptr<MyClass> MyClassPtr;
typedef atomic<MyClassPtr> MyClassAtomicPtr;
// 1.
MyClassPtr ptr( new MyClass() );
printf("1. use_count=%d\n", ptr.use_count());
// 2.
MyClassAtomicPtr atomicPointer(ptr);
printf("2. use_count=%d\n", ptr.use_count());
// 3.
{
MyClassPtr p = atomicPointer.load();
printf("3a. use_count=%d\n", ptr.use_count());
}
printf("3b. use_count=%d\n", ptr.use_count());
// 4.
{
MyClassPtr p = atomicPointer.load();
printf("4a. use_count=%d\n", ptr.use_count());
}
printf("4b. use_count=%d\n", ptr.use_count());
这个输出是:
1. use_count=1
2. use_count=2
3a. use_count=2
3b. use_count=1
4a. use_count=1
4b. use_count=-572662307
我理解第1步和第2步。但是在第3步,我希望对shared_ptr的赋值将ref-count增加到3,然后当它超出ref-count范围时返回到2.但实际上它在分配时保持为2,然后当shared_ptr超出范围时减少到1。类似地,在步骤4中,ref-count变为零并且对象被删除。
所以我的问题是:如何在不破坏原子的情况下访问和使用原子管理的shared_ptr?
(我正在使用Visual Studio 2012版本11.0.50727.1 RTMREL进行编译)
答案 0 :(得分:8)
您不能将std::shared_ptr<T>
用作std::atomic<T>
的模板参数类型。 “模板参数T的类型应该是可以轻易复制的。” (N3290中的§29.51)std::shared_ptr<T>
并非易于复制。
显然,在您的示例中std::memcpy
(或类似的东西)用于复制std::shared_ptr
,然后调用析构函数。这就是递减引用计数的原因。在最后一步中,该对象将被删除。
解决方案是使用std::mutex
来保护您的std::shared_ptr
。
答案 1 :(得分:5)
我认为原子加载和存储共享指针的标准方法是使用§20.7.2.5[util.smartptr.shared.atomic]中的函数。似乎只有clac的libc ++支持它们:
template<class T> bool atomic_is_lock_free(const shared_ptr<T>* p);
template<class T> shared_ptr<T> atomic_load(const shared_ptr<T>* p);
template<class T> shared_ptr<T> atomic_load_explicit(const shared_ptr<T>* p, memory_order mo);
template<class T> void atomic_store(shared_ptr<T>* p, shared_ptr<T> r);
template<class T> void atomic_store_explicit(shared_ptr<T>* p, shared_ptr<T> r, memory_order mo);
template<class T> shared_ptr<T> atomic_exchange(shared_ptr<T>* p, shared_ptr<T> r);
template<class T> shared_ptr<T> atomic_exchange_explicit(shared_ptr<T>* p, shared_ptr<T> r, memory_order mo);
template<class T> bool atomic_compare_exchange_weak(shared_ptr<T>* p, shared_ptr<T>* v, shared_ptr<T> w);
template<class T> bool atomic_compare_exchange_strong(shared_ptr<T>* p, shared_ptr<T>* v, shared_ptr<T> w);
template<class T> bool atomic_compare_exchange_weak_explicit(shared_ptr<T>* p, shared_ptr<T>* v, shared_ptr<T> w, memory_order success, memory_order failure);
template<class T> bool atomic_compare_exchange_strong_explicit(shared_ptr<T>* p, shared_ptr<T>* v, shared_ptr<T> w, memory_order success, memory_order failure);
所以你的代码可以写成:
auto ptr = std::make_shared<MyClass>();
printf("1. use_count=%d\n", ptr.use_count());
{
auto p = std::atomic_load(&ptr);
printf("3a. use_count=%d\n", ptr.use_count());
}
printf("3b. use_count=%d\n", ptr.use_count());
{
auto p = std::atomic_load(&ptr);
printf("3a. use_count=%d\n", ptr.use_count());
}
printf("4b. use_count=%d\n", ptr.use_count());
但是我无法在MSDN上找到这样的支持,所以你能做的最好就是使用互斥锁。 (实际上,libc ++中这些函数的实现也使用了互斥锁。)
答案 2 :(得分:1)
在实现的内容中,你正在调用的std :: atomic ctor必须为其内部指针分配如下内容:
std::atomic(T* ctorInput) {
memcpy(myPtr, ctorInput, sizeof(T));
}
这意味着它直接复制字节,绕过任何真正的复制构造函数&#34; T(const T&amp;)&#34;。这就是为什么它只能在一个简单的可复制的&#39;类型,即复制构造函数无论如何都不做任何事情的类型。由于shared_ptr DOES在其副本中执行实际工作,即原子增量,因为它从不调用调用,因此std :: atomic无法完成工作。然后,您在引用计数中得到一个神秘的1分之一错误。
答案 3 :(得分:1)
在 C++20
MSVC(VS 版本 16.7
)中支持 std::atomic<std::shared_ptr<T>>
。
下面我在做了一些原子 loads
后打印出引用计数。
请注意,在撰写本文时,clang 和 gcc 均不支持此功能。
std::atomic<std::shared_ptr<int>> pointer{ std::make_shared<int>( 42 ) };
{
auto p1{ pointer.load( ) };
// Prints 2.
std::cout << std::format( "Count: {}\n", p1.use_count( ) );
}
auto p2{ pointer.load( ) };
// Prints 2.
std::cout << std::format( "Count: {}\n", p2.use_count( ) );
// Prints 3.
auto p3{ pointer.load( ) };
std::cout << std::format( "Count: {}\n", p3.use_count( ) );