std::atomic<T> 模板删除了它的复制构造函数,因为原子用于共享状态,所以将它们复制到另一个原子通常不是你想要的。
删除复制构造函数会迫使您的类的用户思考他们在做什么,并记录他们正在执行一个值的原子加载,然后将该副本传递到其他地方。 (例如atomic<some_struct> var1 (var2.load()))。见C++11: write move constructor with atomic<bool> member?
std::atomic<T>is not itself atomic 的构造函数,所以在你的构造函数中担心存储的顺序是没有意义的(除非你的构造函数调用了一堆其他函数并将mInt 的地址放在某个地方另一个线程可以得到它...)
更好的是,使用复制的值作为初始值设定项,而不是进行原子存储。 (另见Nonlocking Way to Copy Atomics in Copy Constructor)。
我认为这可能是一个问题的唯一方法是,如果您正在做一些已经未定义的行为,例如使用 placement-new 在可以读取/写入的已共享位置构造一个新的 Foo 对象当你这样做时,其他线程。这显然很疯狂,所以不要这样做。
让你的类的内存排序行为匹配 std::atomic<T> 的构造函数(即没有用于存储初始化程序)似乎是个好主意。
只有调用者知道从源操作数加载是否需要顺序一致性。因此,您应该让调用者通过接受一个内存顺序参数来选择,默认 = seq_cst(为了与std::atomic 保持一致,而不是因为在这种情况下任何人都可能想要这样)。是的,这是合法的 C++:copy constructor with default arguments
#include <atomic>
struct Foo
{
std::atomic<int> mInt;
Foo() {}
Foo(const Foo& pOther, std::memory_order order = std::memory_order_seq_cst)
: mInt(pOther.mInt.load(order))
{}
};
这符合我的预期:对加载进行排序,但对存储没有排序。 (例如,查看 ARM64 的 asm 输出显示加载使用 ldar 进行获取加载,但存储只是一个简单的 str)。
我用这个调用者(Godbolt compiler explorer) 对其进行了测试,它在堆栈上构造一个,然后将其地址传递给一个非内联函数,该函数可能使该地址可用于其他线程。所以它无法优化。
void extf(Foo &); // non-inline function
void test(const Foo *p) {
Foo tmp(*p);
extf(tmp);
}
无论extf() 为使该地址对其他线程可用所做的任何事情,都应使用释放存储,以确保看到该地址的任何其他线程将看到正确构造的Foo。这是一个正常的要求,这就是为什么初始化器甚至不是原子的完全没问题。
请注意,作为单个原子操作(在 C++11 或我知道的任何硬件上)不可能在两个不同的内存位置之间进行移动,因此强排序不太可能有用。
即使定义这样的移动是否是原子的也是有问题的,因为原子性只存在于观察者的眼中。由于不可能同时观察两个内存位置,因此这是一个毫无意义的概念。 (除非它们是相邻的,并且您可以通过单个原子负载同时获得它们)。