`unique_ptr`的原子操作

时间:2016-01-28 17:09:02

标签: c++ multithreading thread-safety atomic smart-pointers

std::shared_ptr has specializations for atomic operationsatomic_compare_exchange_weak和家人一样,但我无法找到有关std::unique_ptr的等效专精的文档。有吗?如果没有,为什么不呢?

3 个答案:

答案 0 :(得分:8)

std::unique_ptr没有标准的原子函数。

我确实在Herb Sutter的Atomic Smart Pointers(N4058)中找到了一个论据。

  

劳伦斯·克劳尔回答说:

     

shared_ptr锁定的原因之一是避免我们削弱原子模板参数的前提条件,这是一件小事,因此没有死锁的风险。

     

也就是说,我们可以削弱需求,这样参数类型只需要是lockfree,或者只是非递归锁定。

     

然而,虽然微不足道可以产生合理可测试的特征,但我认为没有有效的机制来测试较弱的属性。

该提案已分配给并发子组,并且尚未配置。您可以在JTC1/SC22/WG21 - Papers 2014 mailing2014-07

查看状态

答案 1 :(得分:6)

要小心,在线程之间共享一个可修改的unique_ptr很少有意义,即使指针本身是原子的。如果其内容发生变化,其他线程如何知道呢?他们不能。

考虑这个例子:

unique_ptr<MyObject> p(new MyObject);

// Thread A
auto ptr = p.get();
if (ptr) {
    ptr->do_something();
}

// Thread B
p.reset();

线程A如何在调用p.get()后避免使用悬空指针?

如果要在线程之间共享对象,请使用shared_ptr,其中引用计数完全用于此目的。

如果真的想要它,你可以随时推出自己的atomic_unique_ptr,这就行了(简化):

#pragma once
#include <atomic>
#include <memory>

template<class T>
class atomic_unique_ptr
{
  using pointer = T *;
  std::atomic<pointer> ptr;
public:
  constexpr atomic_unique_ptr() noexcept : ptr() {}
  explicit atomic_unique_ptr(pointer p) noexcept : ptr(p) {}
  atomic_unique_ptr(atomic_unique_ptr&& p) noexcept : ptr(p.release()) {}
  atomic_unique_ptr& operator=(atomic_unique_ptr&& p) noexcept { reset(p.release()); return *this; }
  atomic_unique_ptr(std::unique_ptr<T>&& p) noexcept : ptr(p.release()) {}
  atomic_unique_ptr& operator=(std::unique_ptr<T>&& p) noexcept { reset(p.release()); return *this; }

  void reset(pointer p = pointer()) { auto old = ptr.exchange(p); if (old) delete old; }
  operator pointer() const { return ptr; }
  pointer operator->() const { return ptr; }
  pointer get() const { return ptr; }
  explicit operator bool() const { return ptr != pointer(); }
  pointer release() { return ptr.exchange(pointer()); }
  ~atomic_unique_ptr() { reset(); }
};

template<class T>
class atomic_unique_ptr<T[]> // for array types
{
  using pointer = T *;
  std::atomic<pointer> ptr;
public:
  constexpr atomic_unique_ptr() noexcept : ptr() {}
  explicit atomic_unique_ptr(pointer p) noexcept : ptr(p) {}
  atomic_unique_ptr(atomic_unique_ptr&& p) noexcept : ptr(p.release()) {}
  atomic_unique_ptr& operator=(atomic_unique_ptr&& p) noexcept { reset(p.release()); return *this; }
  atomic_unique_ptr(std::unique_ptr<T>&& p) noexcept : ptr(p.release()) {}
  atomic_unique_ptr& operator=(std::unique_ptr<T>&& p) noexcept { reset(p.release()); return *this; }

  void reset(pointer p = pointer()) { auto old = ptr.exchange(p); if (old) delete[] old; }
  operator pointer() const { return ptr; }
  pointer operator->() const { return ptr; }
  pointer get() const { return ptr; }
  explicit operator bool() const { return ptr != pointer(); }
  pointer release() { return ptr.exchange(pointer()); }
  ~atomic_unique_ptr() { reset(); }
};

注意:此帖中提供的代码现已发布到公共领域。

答案 2 :(得分:5)

可能提供std::shared_ptr的原子实例且std::unique_ptr无法执行此操作的原因在其签名中暗示。比较:

  • std::shared_ptr<T> vs
  • std::unique_ptr<T, D>其中D是删除者的类型。

std::shared_ptr需要分配一个控制块,其中保留强弱计数,因此删除器的类型擦除是以微不足道的代价(一个稍微大一点的控制块)。

因此,std::shared_ptr<T>的布局通常类似于:

template <typename T>
struct shared_ptr {
    T* _M_ptr;
    SomeCounterClass<T>* _M_counters;
};

并且可以原子地执行这两个指针的交换。

std::unique_ptr有一个零开销政策;与使用原始指针相比,使用std::unique_ptr不会产生任何开销。

因此,std::unique_ptr<T, D>的布局通常类似于:

template <typename T, typename D = default_delete<T>>
struct unique_ptr {
    tuple<T*, D> _M_t;
};

tuple使用EBO(空基优化)时,只要D为零,然后sizeof(unique_ptr<T>) == sizeof(T*)

但是,在D不是零大小的情况下,实现归结为:

template <typename T, typename D = default_delete<T>>
struct unique_ptr {
    T* _M_ptr;
    D _M_del;
};

D是踢球者;一般来说,不可能保证D可以在不依赖互斥体的情况下以原子方式进行交换。

因此,无法为通用std::atomic_compare_exchange*提供std::unique_ptr<T, D>套专业例程。

请注意,该标准甚至不保证sizeof(unique_ptr<T>) == sizeof(T*) AFAIK,尽管它是一种常见的优化。