为什么 std::atomic 的默认构造函数不默认初始化底层存储值?

Why does default constructor of std::atomic not default initialize the underlying stored value?

今天是美国的感恩节,我会被指定火鸡来问这个问题:

像这样无伤大雅的事情。具有简单普通旧数据类型的原子,例如 int:

atomic<int> x;
cout << x;

以上将打印出垃圾(未定义)数据。考虑到我为 atomic constuctor:

阅读的内容,这是有道理的

(1) default constructor

Leaves the atomic object in an uninitialized state. An uninitialized atomic object may later be initialized by calling atomic_init.

感觉像是一个奇怪的委员会决定。但我相信他们有他们的理由。但我想不出另一个 std:: class 默认构造函数会使对象处于未定义状态。

我可以看到对于没有默认构造函数且需要走 atomic_init 路径的 std::atomic 更复杂的类型有何意义。但更一般的情况是使用具有简单类型的原子,用于引用计数、顺序标识符值和基于简单轮询的锁定等场景。因此,这些类型没有自己的存储值 "zero-initialized"(默认初始化)感觉很奇怪。或者至少,如果无法预测,为什么要有默认构造函数。

未初始化的 std::atomic 实例会有用的原因是什么。

mentioned in P0883一样,这种行为的主要原因是与C的兼容性。显然C没有值初始化的概念; atomic_int i; 不执行初始化。为了与 C 兼容,等效的 C++ 也必须不执行初始化。由于 C++ 中的 atomic_int 应该是 std::atomic<int> 的别名,因此为了完全 C/C++ 兼容性,该类型也必须不执行初始化。

幸运的是,C++20 looks to be undoing this behavior

What's the rationale for this where an uninitialized std::atomic instance would be useful.

出于同样的原因 基本 "building block" 用户定义类型不应超过严格需要,尤其是在构造等不可避免的操作中。

But I can't think of another std:: class where the default constructor will leave the object in an undefined state.

所有不需要内部不变量的 类 都是这种情况。

泛型代码中没有期望 T x; 会创建零初始化对象;但预计它将创建一个可用状态的对象。对于标量类型,任何现有对象在其生命周期内都是可用的。

另一方面,预计

T x = T();

将为泛型代码、普通值类型创建一个处于默认状态的对象。 (如果表示的值有这样的东西,通常是 "zero value"。)

原子非常不同,它们以不同的方式存在"world"

原子并不是真正关于值的范围。它们旨在为读取、写入和复杂操作提供特殊保证; 原子在很多方面不同于其他数据类型,因为没有根据对该对象的正常赋值定义复合赋值操作。所以通常的等价物不适用于原子。 你不能像对普通对象那样对原子进行推理。

您根本无法在原子和普通对象上编写通用代码;这将毫无意义。

(见脚注。)

总结

  • 您可以拥有通用代码,但不能拥有原子-非原子通用算法,因为它们的语义不属于同一风格的语义定义(甚至不清楚 C++ 如何同时具有原子和非原子操作).
  • "You don't pay for what you don't use."
  • 没有通用代码会假设未初始化的变量有值;只是它处于赋值和其他不依赖于先前值的操作的有效状态(显然没有复合赋值)。
  • 许多 STL 类型未被其默认构造函数初始化为 "zero" 或默认值。

[脚注:

以下是"a rant"技术性重要的文字,但对于理解原子对象的构造函数为什么是这样并不重要

它们只是以最深入的方式遵循不同的语义规则:标准甚至没有描述的方式,因为标准从未解释多线程的最基本事实:语言的某些部分被评估作为一系列取得进展的操作,而其他领域(原子,try_lock ...)则没有。 事实上,标准的作者显然甚至没有看到这种区别,甚至不理解这种二元性。 (请注意,讨论这些问题通常会使您的问题和答案遭到否决和删除。)

这种区别是必不可少的,因为没有它(同样,它在标准中也没有出现),零个程序甚至可以具有多线程定义的行为:没有这种二元性,只能解释旧式线程前行为。

C++ 委员会不了解 C++ 的症状是他们认为 "no thin air value" 是一个额外的功能,而不是语义的重要部分(没有得到 "no thin air" 保证原子使顺序程序的顺序语义承诺更加站不住脚)。

--尾注]