C++ 与std::shared\u ptr一起使用时,原子加载方法会减少引用计数

C++ 与std::shared\u ptr一起使用时,原子加载方法会减少引用计数,c++,c++11,shared-ptr,atomic,C++,C++11,Shared Ptr,Atomic,我想在我的代码中使用std::atomic,以便可以原子地更新共享的\u ptr,但是我在访问共享的\u ptr时遇到了问题。原子上的load()方法似乎减少了共享_ptr上的ref计数,因此如果不释放对象,我就无法实际使用它 下面是一段简化的代码,它显示了这个问题 typedef shared_ptr<MyClass> MyClassPtr; typedef atomic<MyClassPtr> MyClassAtomicPtr; // 1. MyClassPtr p

我想在我的代码中使用
std::atomic
,以便可以原子地更新共享的\u ptr,但是我在访问共享的\u ptr时遇到了问题。原子上的load()方法似乎减少了共享_ptr上的ref计数,因此如果不释放对象,我就无法实际使用它

下面是一段简化的代码,它显示了这个问题

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步和第2步。但是在第3步,我希望分配给共享的\u ptr的ref计数增加到3,然后当它超出范围时,ref计数返回到2。但事实上,当分配时,它保持在2,当共享的ptr超出范围时,它会减少到1。类似地,在步骤4中,ref计数变为零,对象被删除

所以我的问题是:我如何访问和使用原子管理的共享_ptr而不破坏它


(我是用Visual Studio 2012 11.0.50727.1版RTMREL编译的)

您不能使用
std::shared\u ptr
作为
std::atomic
的模板参数类型。“模板参数T的类型应具有可复制性。”(§N3290中的§29.5 1)
std::shared_ptr
不具有可复制性

显然,在您的示例中,
std::memcpy
(或类似的东西)用于复制
std::shared_ptr
,然后调用析构函数。这就是引用计数递减的原因。在最后一步中,将删除该对象


解决方案是使用
std::mutex
来保护您的
std::shared_ptr

我认为以原子方式加载和存储共享指针的标准方法是使用§20.7.2.5[util.smartptr.shared.atomic]中的函数。似乎只有clang的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);
模板bool-atomic_是无锁的(const-shared_-ptr*p);
模板共享\u ptr原子负载(常量共享\u ptr*p);
模板共享\u ptr原子\u加载\u显式(常量共享\u ptr*p,内存\u顺序mo);
模板无效原子_存储(共享_ptr*p,共享_ptr);
模板无效原子存储显式(共享内存顺序mo);
模板共享\u ptr原子交换(共享\u ptr*p,共享\u ptr);
模板共享\u ptr原子交换\u显式(共享\u ptr*p、共享\u ptr、内存\u顺序mo);
模板布尔原子比较交换弱(共享ptr*p、共享ptr*v、共享ptr w);
模板布尔原子比较交换强(共享ptr*p、共享ptr*v、共享ptr w);
模板布尔原子比较交换弱显式(共享ptr*p、共享ptr*v、共享ptr w、内存顺序成功、内存顺序失败);
模板布尔原子比较交换强显式(共享ptr*p、共享ptr*v、共享ptr w、内存顺序成功、内存顺序失败);
因此,您可以将代码编写为:

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());
auto ptr=std::make_shared();
printf(“1.使用计数=%d\n”,ptr.使用计数();
{
自动p=std::原子加载(&ptr);
printf(“3a.use_count=%d\n”,ptr.use_count());
}
printf(“3b.使用计数=%d\n”,ptr.使用计数();
{
自动p=std::原子加载(&ptr);
printf(“3a.use_count=%d\n”,ptr.use_count());
}
printf(“4b.使用计数=%d\n”,ptr.使用计数();

但是我在MSDN上找不到这样的支持,所以最好使用互斥锁。(实际上,这些函数在libc++中的实现也使用互斥锁。)

在实现过程中,您要调用的std::atomic ctor必须为其内部指针分配如下内容:

std::atomic(T* ctorInput) {
   memcpy(myPtr, ctorInput, sizeof(T));
}

这意味着它在字节上直接复制,绕过任何真正的复制构造函数“T(const T&)”。这就是为什么它只能在“可复制的”类型上正确工作的原因,也就是说,它的复制构造函数无论如何都不做任何事情。由于shared_ptr在其copy-ctor中确实做了实际的工作,即原子增量,因此std::atomic无法完成工作,因为它从不调用调用。然后,在引用计数中会出现一个神秘的off-by-1错误

使用管理对
std::shared_ptr
的原子访问的自由函数,而不是互斥。感谢您,nosid,解释了为什么ref计数没有像我预期的那样工作。我想象如果模板专门化存在(参考KennyTM的答案),那么它就会工作?@Pete Becker:当你谈论“自由函数”时,你是指KennyTM的答案中讨论的原子加载专门化,还是其他什么?不幸的是,VS2012没有这些专门化,但是如果有另一种方法来管理(更有效的)对共享的\u ptr的原子更新而不使用互斥,我会非常感兴趣;它们当然存在于我为纯粹软件编写的代码中,这就是微软提供的库。试试看;不在MSDN中意味着他们不在MSDN中。谢谢。我也尝试过原子_load()函数,我发现它们也不起作用。正如您所说,看起来他们没有VS2012中应有的共享ptr专业化。如果它们在那里,你会不得不使用原子加载(&ptr)调用它们吗,或者它也可以与原子一起工作吗?我的错误:在我看来,原子加载的专门化似乎在那里。这是来自
标题:
共享的ptr原子负载(const shared ptr*\ptr)
,它看起来像是用一个自旋锁实现的。我可以确认这确实是用VC++2012 RTM编译和运行的,这只是一个文档错误。Herb Sutter为原子共享的ptr提供了一个C++(17?)标准设计方案,该文件还解释了现有方法的缺点:
std::atomic(T* ctorInput) {
   memcpy(myPtr, ctorInput, sizeof(T));
}