6

我想std::atomic<std::shared_ptr>在我的代码中使用 a 以便可以自动更新 shared_ptr,但是在访问 shared_ptr 时我遇到了问题。atomic 上的 load() 方法似乎减少了 shared_ptr 上的引用计数,因此如果没有释放它,我实际上无法使用该对象。

这是显示问题的简化代码...

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 步中,引用计数变为零并且对象被删除。

所以我的问题是:如何访问和使用由 atomic 管理的 shared_ptr 而不会破坏它?

(我正在使用 Visual Studio 2012 版本 11.0.50727.1 RTMREL 进行编译)

4

4 回答 4

8

您不能std::shared_ptr<T>std::atomic<T>. “模板参数 T 的类型应该是可简单复制的。” (N3290 中的§29.5 1)std::shared_ptr<T>不可轻易复制。

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

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

于 2012-11-04T10:16:03.183 回答
5

我相信以原子方式加载和存储共享指针的标准方法是使用 §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);

所以你的代码可以写成:

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++ 中的实现也使用了互斥锁。)

于 2012-11-04T10:26:02.307 回答
1

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

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

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

于 2014-08-07T15:23:59.380 回答
1

C++20MSVC(VS 版本16.7)中支持std::atomic<std::shared_ptr<T>>.

下面我在做了一些 atomic 之后打印出引用计数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( ) );
于 2021-07-22T01:37:39.137 回答