“unique_ptr”上的原子操作

Atomic operations on `unique_ptr`

本文关键字:原子操作 ptr unique      更新时间:2023-10-16

std::shared_ptr具有原子操作的专用化,例如atomic_compare_exchange_weak和家庭,但我找不到有关std::unique_ptr等效专业化的文档。有吗?如果没有,为什么不呢?

可以在

std::shared_ptr提供原子实例并且不可能为std::unique_ptr这样做的原因在他们的签名中有所暗示。比较:

  • std::shared_ptr<T>
  • 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::unique_ptr<T, D>提供一套std::atomic_compare_exchange*专用例程。

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

不,没有标准的原子函数用于std::unique_ptr

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

劳伦斯·克劳尔回应补充说:

shared_ptr锁定的原因之一是为了避免我们削弱原子模板参数的前提条件的情况,即它是微不足道的,因此没有死锁的风险。

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

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

该建议已分配给并发子组,目前尚未处置。 您可以在JTC1/SC22/WG21 - 论文2014-07邮寄2014-07查看状态

请注意,在线程之间共享可修改的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(); }
};

注意:本文中提供的代码特此发布到公共领域。