存储后的 std::atomic 地址

j5w*_*j5w 1 c++ operator-overloading language-lawyer stdatomic c++17

我似乎无法在商店之后获得原子对象的地址。

例如

std::atomic<int> i;
std::atomic<int>* p = &++i; // doesn't work
auto* p = &++i; // doesn't work
// below works:
++i;
auto* p = &i;
Run Code Online (Sandbox Code Playgroud)

这里发生了什么,为什么?

澄清一下:我知道它返回一个 r 值。为什么它不返回原始对象,this?这是一个有目的的设计选择还是一个疏忽?

更具体地说,这个要求的幕后发生了什么?

jtb*_*des 6

虽然预增量运算符通常通过引用返回其操作数,但在std::atomic整数的情况下,它将新值作为临时值返回。因此,在您的示例++i中,不会返回对atomic<int> i自身的引用,而是返回i(即 int)的新值。您可以在以下位置查看:https : //en.cppreference.com/w/cpp/atomic/atomic/operator_arith

返回对原始 的引用会产生误导甚至危险atomic<int>,因为通过该引用访问 int 值需要第二次单独的读取操作——因此它的值可能与增量时的值不同。(这与您的示例代码并不是特别相关,因为您只是试图获取指向被引用对象的指针,但某些代码实际上会在之后访问该值,++因此这就是无法返回引用的原因。)

换句话说,如果++i返回对 的引用atomic<int> i,则

int j = ++i;
Run Code Online (Sandbox Code Playgroud)

将相当于

++i;
// ...other threads may modify the value of `i` here!...
int j = i;
Run Code Online (Sandbox Code Playgroud)

原子的全部意义在于将读取和写入作为不可分割的操作一起执行,因此++i必须在内部使用硬件/操作系统原子操作来同时读取和递增整数,因此新值作为临时返回。

如果您很想知道幕后是什么,这里是 libc++ 的实现,您可以在其中看到operator++简单地调用fetch_add(1)并返回结果 + 1。

  • *原子的全部意义在于将读取和写入作为不可分割的操作一起执行* - 不太正确。另一个重要的用例是纯加载和纯存储操作。有些算法不需要对一个对象进行任何 RMW,但需要对其进行原子加载和存储(无撕裂)并且通常也是有序的。在 C++ 级别,不受 UB 的影响,因此使用“易失性”自行开发几乎总是一个坏主意。(当然,在实践中,仅使用普通变量完全被常见的优化所破坏,例如将值保留在寄存器中,这是没有数据争用 UB 的假设所允许的) (3认同)
  • 这在技术上可能是可行的,但我认为这不会真正带来更安全、更易读的代码;相反,它会让事情更容易出现微妙的错误(并使库变得复杂)。 (2认同)
  • @lajoh90686:因为优化原子是一个语言标准+编译器尚未解决的难题。[编译器是否可以优化两个原子加载?](/sf/ask/2927437761/)。在这种情况下,会有什么好处呢?为了使其可用,您需要*保证*获得与`++i`存储的相同值,而不仅仅是编译器以这种方式优化的*选项*,因此返回更有意义按值传递“T”,而不是“atomic&lt;T&gt;”引用。 (2认同)