为什么 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
) 将在以下两种情况下生成不同的程序集,
每个成员函数都以相同的主体出现,有和没有 volatile
,
只有 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
的东西,并且出于某种原因您无法修改它。很难想象,但我想可能需要在 atomic
和 volatile
内联汇编之间有某种保证顺序。恕我直言,最重要的是,只要有可能,您就可以使用新的原子库而不是 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
).
我注意到大多数 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
) 将在以下两种情况下生成不同的程序集,
每个成员函数都以相同的主体出现,有和没有
volatile
,只有
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
的东西,并且出于某种原因您无法修改它。很难想象,但我想可能需要在 atomic
和 volatile
内联汇编之间有某种保证顺序。恕我直言,最重要的是,只要有可能,您就可以使用新的原子库而不是 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
).