为什么 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++ 兼容性,该类型也必须不执行初始化。
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" 保证原子使顺序程序的顺序语义承诺更加站不住脚)。
--尾注]
今天是美国的感恩节,我会被指定火鸡来问这个问题:
像这样无伤大雅的事情。具有简单普通旧数据类型的原子,例如 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++ 兼容性,该类型也必须不执行初始化。
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" 保证原子使顺序程序的顺序语义承诺更加站不住脚)。
--尾注]