为什么 std::atomic 中的所有成员函数都出现有和没有 volatile?

Why do all the member functions in std::atomic appear both with and without volatile?

我注意到大多数 std::atomic<T> 类型的成员函数被声明了两次,一次带有 volatile 修饰符,一次没有 (example))。我检查了 G++ 标准库实现的源代码,发现它们都是完全相同的,例如,

bool
load(memory_order __m = memory_order_seq_cst) const noexcept
{ return _M_base.load(__m); }

bool
load(memory_order __m = memory_order_seq_cst) const volatile noexcept
{ return _M_base.load(__m); }

我找不到任何示例,其中 volatile 变体与非 volatile 变体的行为不同,在 return 类型或任何类似的方面有所不同。

这是为什么?我认为 volatile 成员函数也可以在不是 volatile 的对象中调用。所以声明和定义 std::atomic::load(...) const volatile noexcept 等应该就足够了。

更新:

根据评论,我的问题基本上可以归结为:您能否提供一个示例,其中某些调用使用 non-volatile 实例(不一定是 std::atomic) 将在以下两种情况下生成不同的程序集,

  1. 每个成员函数都以相同的主体出现,有和没有 volatile

  2. 只有 volatile 变种存在?

假设编译器可以进行标准允许的任何优化(或简单的最高优化级别)。

可能这一切都源于 volatile 是什么,请参阅 this answer。由于与通常的应用程序开发相比,用例非常少,这就是为什么通常没人关心的原因。我假设您没有任何实际场景想知道是否应该应用那些易失性重载。然后我会尝试想出一个你可能需要的例子(不要判断它太真实)。

volatile std::sig_atomic_t status = ~SIGINT;
std::atomic<int> shareable(100);

void signal_handler(int signal)
{
  status = signal;
}

// thread 1
  auto old = std::signal(SIGINT, signal_handler);
  std::raise(SIGINT);
  int s = status;
  shareable.store(10, std::memory_order_relaxed);
  std::signal(SIGINT, old);

// thread 2
  int i = shareable.load(std::memory_order_relaxed);

memory_order_relaxed保证原子性和修改顺序的一致性,无副作用。 volatile 无法重新排序,但有副作用。然后我们到了,在线程 2 中你可以得到 shareable 等于 10,但状态仍然不是 SIGINT。但是,如果将类型限定符设置为 volatile of shareable 则必须保证。为此,您需要成员方法是 volatile-qualified.

你为什么要做这样的事情?我可能想到的一种情况是,您有一些旧代码正在使用旧的基于 volatile 的东西,并且出于某种原因您无法修改它。很难想象,但我想可能需要在 atomicvolatile 内联汇编之间有某种保证顺序。恕我直言,最重要的是,只要有可能,您就可以使用新的原子库而不是 volatile 对象,如果有一些 volatile 对象,您无法摆脱,并且您想要使用 atomic 对象,那么您可能需要 volatile 限定符来使 atomic 对象具有正确的顺序保证,因为您需要重载。

更新

But if all I wanted was to have atomic types usable as both volatile and non-volatile, why not just implement the former?

struct Foo {
    int k;
};

template <typename T>
struct Atomic {
  void store(T desired) volatile { t = desired; }
  T t;
};

int main(int i, char** argv) {
    //error: no viable overloaded '='
    // void store(T desired) volatile { t = desired; }
  Atomic<Foo>().store(Foo());
  return 0;
}

load 和其他操作也是如此,因为这些通常不是简单的实现,需要复制运算符 and/or 复制构造函数(也可以 volatile 或非 volatile).