当与std :: shared_ptr一起使用时,std :: atomic load方法会减少引用计数

时间:2012-11-04 08:40:51

标签: c++ c++11 shared-ptr atomic

我想在我的代码中使用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进行编译)

4 个答案:

答案 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( ) );