我注意到大多数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
变体表现不同,返回类型或类似类型有所不同的任何示例。
那是为什么?我认为volatile
成员函数也可以在不是volatile
的对象中调用。因此,声明和定义std::atomic::load(...) const volatile noexcept
等就足够了。
更新:
根据评论,我的问题基本上可以归结为:您能否提供一个示例,其中使用非-volatile
实例(不一定是std::atomic
)进行某些调用在以下两种情况下生成不同的程序集,
每个成员函数在具有和没有volatile
的情况下都具有相同的主体,
仅存在volatile
变体?
这是假设编译器可以执行标准允许的任何优化(或只是最高优化级别)。
答案 0 :(得分:1)
可能全部源于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
中的shareable
,则必须保证。为此,您将需要volatile
限定成员方法。
您为什么要这样做?我可能想到的一种情况是,您有一些使用基于volatile
的旧代码的旧代码,并且由于一个或另一个原因而无法对其进行修改。难以想象,但是我想在atomic
和volatile
内联汇编之间可能需要某种保证顺序。底线,恕我直言,是只要有可能,您就可以使用新的原子库而不是volatile
对象,如果有一些volatile
对象是您无法摆脱的,并且您想使用atomic
对象,那么您可能需要volatile
限定符来使atomic
对象具有适当的顺序保证,因为这将需要重载。
更新
但是,如果我只想让原子类型既可以使用易失性也可以使用非易失性,那么为什么不仅仅实现前者呢?
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
和其他操作也一样,因为这些通常不是需要复制操作符和/或复制构造函数(也可以是volatile
或非volatile
的简单实现。 )。