The std :: atomic load method decreases the reference count when used with std :: shared_ptr

I would like to use std::atomic<std::shared_ptr> in my code so that shared_ptr can be atomically updated, but I have a problem accessing shared_ptr. The load () method on the atom seems to decrease ref-count by shared_ptr, so I cannot actually use the object without releasing it.

Here is a simplified piece of code that shows the problem ...

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

The result of this:

 1. use_count=1 2. use_count=2 3a. use_count=2 3b. use_count=1 4a. use_count=1 4b. use_count=-572662307 

I understand steps 1 and 2. But in step 3, I would expect the assignment of shared_ptr to increment the reference count to 3, and then when it disappears from the reference area to go back to 2. But in fact, it remains at 2 when assigned and then decreases to 1 when shared_ptr goes beyond. Similarly, in step 4, where the ref count vanishes and the object is deleted.

So my question is: how can I access and use atom_ controlled shared_ptr without destroying it?

(I compiled with Visual Studio 2012 version 11.0.50727.1 RTMREL)

+6
source share
3 answers

I believe that the standard way to atomically load and store shared pointers is to use the functions in §20.7.2.5 [util.smartptr.shared.atomic]. It seems only libC ++ clang supports them:

 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); 

So, the code can be written as:

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

But I can not find such support listed in MSDN, so it is best to use a mutex. (In fact, the implementation of these functions in libC ++ also uses the mutex.)

+4
source

You cannot use std::shared_ptr<T> as the template argument type for std::atomic<T> . "The template argument type T must be trivially copied." (§29.5 1 in N3290) std::shared_ptr<T> not trivially copied.

Apparently, in your example, std::memcpy (or something like that) is used to copy std::shared_ptr , and then the destructor is called. This is the reason for decremented reference counting. In the last step, the object is deleted.

The solution is to use std::mutex to protect your std::shared_ptr .

+8
source

Going into the courage of the implementation, the called std :: atomic ctor should assign an internal pointer with something like:

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

This means that it makes a direct copy of the bytes, bypassing any real copy instance of "T (const T &)". That's why it works correctly only with the help of a “trivially copied” type, namely: the one whose copy constructor does nothing. Since shared_ptr does the real job, namely the atomic increment, in its copy of ctor this work is not performed by std :: atomic because it never calls. Then you get a cryptic error “for-1” in the reference count.

+1
source

All Articles