Search code examples
c++c++11shared-ptratomic

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


I would like to use a std::atomic<std::shared_ptr> in my code so that the shared_ptr can be atomicaly updated, but I have a problem when accessing the shared_ptr. The load() method on the atomic seems to reduce the ref-count on the shared_ptr, so that I can't actually use the object without it being deallocated.

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 output of this is:

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 at step 3, I would expect the assignment to the shared_ptr to increase the ref-count to 3, and then when it goes out of scope for the ref-count to go back down to 2. But in fact it stays at 2 when assigned and then decreases to 1 when the shared_ptr goes out of scope. Similarly in step 4, where the ref-count goes to zero and the object is deleted.

So my question is: how can I access and use the shared_ptr managed by the atomic without destroying it?

(I was compiling with Visual Studio 2012 Version 11.0.50727.1 RTMREL)


Solution

  • I believe the standard way to atomically load and store shared pointers are to use the functions in §20.7.2.5[util.smartptr.shared.atomic]. It seems only libc++ of clang support 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 you code could 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't find such supports listed on MSDN, so the best you could do is to use a mutex. (Actually, the implementation of these functions in libc++ uses a mutex too.)