std::shared_ptr
has specializations for atomic operations与atomic_compare_exchange_weak
和家人一样,但我无法找到有关std::unique_ptr
的等效专精的文档。有吗?如果没有,为什么不呢?
答案 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,尽管它是一种常见的优化。