метод std:: atomic load уменьшает количество ссылок при использовании с std:: shared ptr

Я хотел бы использовать std::atomic<std::shared_ptr> в моем коде, чтобы shared_ptr можно было атомарно обновить, но у меня есть проблема при доступе к shared_ptr. Метод load () на atomic, похоже, уменьшает ref-count на shared_ptr, так что я не могу фактически использовать объект без его освобождения.

вот упрощенный фрагмент кода, который показывает проблему...

typedef shared_ptr<MyClass> MyClassPtr;
typedef atomic<MyClassPtr> MyClassAtomicPtr;

// 1.
MyClassPtr ptr( new MyClass() );
printf("1. use_count=%dn", ptr.use_count());

// 2. 
MyClassAtomicPtr atomicPointer(ptr);
printf("2. use_count=%dn", ptr.use_count());

// 3.
{
    MyClassPtr p = atomicPointer.load();
    printf("3a. use_count=%dn", ptr.use_count());
}
printf("3b. use_count=%dn", ptr.use_count());

// 4.
{
    MyClassPtr p = atomicPointer.load();
    printf("4a. use_count=%dn", ptr.use_count());
}
printf("4b. use_count=%dn", 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 при назначении, а затем уменьшается до 1, когда shared_ptr выходит за рамки. Аналогично на шаге 4, где ref-count идет к нулю, и объект удаляется.

Итак, мой вопрос: как я могу получить доступ и использовать shared_ptr, управляемый atomic, не уничтожая его?

(Я был компиляция с Visual Studio 2012 версии 11.0.50727.1 RTMREL)

3 ответов


Я считаю, что стандартный способ атомарной загрузки и хранения общих указателей-использовать функции в §20.7.2.5[util.класса smartptr.общий.атомный.] Кажется, только libc++ clang поддерживает их:

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++ также использует мьютекс.)


вы не можете использовать std::shared_ptr<T> как тип аргумента шаблона std::atomic<T>. "Тип типового аргумента T должен быть тривиально копируемым."(§29.5 1 в N3290) std::shared_ptr<T> не тривиально копируемым.

видимо, в вашем примере std::memcpy (или что-то в этом роде) используется для копирования std::shared_ptr и после этого вызывается деструктор. В этом причина уменьшения количества ссылок. На последнем шаге, объект удаляется.

решение заключается в использовании std::mutex для защитите свой std::shared_ptr.


внизу в кишках реализации std:: atomic ctor, который вы вызываете, должен назначать свой внутренний указатель чем-то вроде:

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

это означает, что он делает прямую копию на байтах, минуя любой реальный конструктор копирования "T(const T&)". Вот почему он работает только правильно на "тривиально копируемом" типе, а именно тот, чей конструктор копирования ничего не делает. Поскольку shared_ptr делает реальную работу, а именно атомарный приращение, в его копии ctor эта работа не выполняется std:: atomic, потому что она никогда не вызывает вызов. Вы тогда получите таинственный на 1 ошибку в счетчик.