为什么std::atomic的默认构造函数不默认初始化底层存储值
Why does default constructor of std::atomic not default initialize the underlying stored value?
由于今天是美国的感恩节,我将成为问这个问题的指定火鸡:
拿一些像这样无害的东西。一个原子,具有简单的普通旧数据类型,如int:
atomic<int> x;
cout << x;
以上内容将打印出垃圾(未定义)数据。考虑到我为原子构造器所读到的内容,这是有道理的
(1)默认构造函数
使原子对象处于未初始化状态。未初始化的原子对象稍后可以通过调用atomic_init进行初始化。
感觉像是一个奇怪的委员会决定。但我相信他们有自己的理由。但我想不出另一个std::
类的默认构造函数会使对象处于未定义状态。
我可以看到,对于std::atomic
使用的更复杂的类型,如果没有默认构造函数,并且需要使用atomic_init
路径,这将是多么有意义。但更常见的情况是,对于引用计数、顺序标识符值和简单的基于轮询的锁定等场景,使用具有简单类型的原子。因此,这些类型没有自己的存储值"零初始化"(默认初始化),这感觉很奇怪。或者至少,如果不可预测,为什么要有一个默认的构造函数。
如果一个未初始化的std::atomic
实例会很有用,那么它的基本原理是什么。
如P0883中所述,这种行为的主要原因是与C兼容。显然,C没有值初始化的概念;atomic_int i;
不进行初始化。为了与C兼容,C++等价物也必须不执行初始化。由于C++中的atomic_int
应该是std::atomic<int>
的别名,因此为了实现完全的C/C++兼容性,该类型也必须不执行初始化。
幸运的是,C++20看起来正在消除这种行为。
未初始化的
std::atomic
实例将是有用的。
出于同样的原因基本的"构建块"用户定义类型不应该做超出严格需要的事情,尤其是在不可避免的操作中,如构建。
但我想不出另一个std::类中有默认构造函数将使对象处于未定义状态。
这是所有不需要内部不变量的类的情况。
在泛型代码中,T x;
不会创建一个初始化为零的对象;但预计它将创建一个处于可用状态的对象。对于标量类型,任何现有对象在其生存期内都是可用的。
另一方面,预计
T x = T();
将为普通值类型的泛型代码创建一个默认状态的对象。(如果所表示的值有这样的东西,它通常会是一个"零值"。)
原子是非常不同的,它们存在于一个不同的"世界">
原子论实际上并不是关于一系列的值。它们是关于为读、写和复杂操作提供特殊保证原子在很多方面不同于其他数据类型,因为从来没有根据对该对象的正常赋值来定义复合赋值操作。所以通常的等价性不适用于原子论在原子论上不能像在普通对象上那样推理
你根本无法在原子和普通对象上编写通用代码;这将永远没有意义。
(见脚注。)
摘要
- 你可以有泛型代码,但不能有原子-非原子泛型算法,因为它们的语义不属于同一风格的语义定义(甚至不清楚C++是如何同时具有原子和非原子操作的)
- "你不会为你不用的东西付钱。">
- 任何泛型代码都不会假定未初始化的变量有一个值;只是它对于赋值和其他不依赖于前一个值的操作处于有效状态(显然没有复合赋值)
- 许多STL类型的默认构造函数不会将其初始化为"零"或默认值
[脚注:
以下是"咆哮",这是一篇技术性的重要文本,但对于理解原子对象的构造函数为何如此并不重要
它们只是以最深刻的方式遵循不同的语义规则:在某种程度上,标准甚至没有描述,因为标准从未解释多线程的最基本事实:语言的某些部分被评估为一系列正在进行的操作,而其他领域(原子学、try_lock…)则没有<事实上,该标准的作者显然甚至没有看到这种区别,甚至不理解这种对偶性。(注意,讨论这些问题通常会让你的问题和答案被否决或删除。)>
这种区别是至关重要的,因为如果没有它(同样,它在标准中也没有出现),完全没有程序可以具有多线程定义的行为:只有老式的线程前行为才能在没有这种双重性的情况下得到解释。
C++委员会没有理解C++的含义的症状是,他们认为"没有稀薄的空气值"是一个额外的功能,而不是语义的重要组成部分(没有为原子论提供"没有稀薄空气"的保证,使得顺序程序的顺序语义的承诺更加站不住脚)。
--尾注]
在C++20中,std::atomic
将默认初始化有限制的值:
- 类型必须是默认的可构造类型
- 初始化是而不是原子
来自cppreference 的报价
- 默认构造函数是琐碎的:除了静态和线程本地对象的零初始化之外,不进行任何初始化。CCD_ 11可以用于完成初始化。(直到C++20)
- 值初始化底层对象(即使用
T()
)。初始化不是原子的。如果std::is_default_constructible_v<T>
是false
,则使用默认构造函数是不正确的。(从C++20开始)
Live Demo比较C++20和C++17
#include <iostream>
#include <atomic>
#include <array>
int main()
{
std::cout << "atomic";
std::array<std::atomic<int>, 5> ar;
for(const auto& item: ar){
std::cout << " " << item;
}
std::cout << "nstatic atomic";
static std::array<std::atomic<int>, 5> sar;
for(const auto& item: sar){
std::cout<< " " << item;
}
}
C++20 的结果
atomic 0 0 0 0 0
static atomic 0 0 0 0 0
C++17的结果(未初始化值为随机垃圾值)
atomic -951377176 32655 4198992 0 0
static atomic 0 0 0 0 0
- 如何创建一个CMake变量,除非显式重写,否则使用默认值
- 将字符串存储在c++中的稳定内存中
- 如何使用默认参数等选择模板专业化
- 具有默认模板参数的多态类的模板推导失败
- std::原子加载和存储都需要吗
- 为什么在没有显式默认构造函数的情况下,将另一个结构封装在联合中作为成员的结构不能编译
- C++:将控制台输出存储在宏中更好吗
- 当函数模板参数是具有默认参数的类模板时,函数模板参数的推导如何执行
- 初始化具有非默认构造函数的std::数组项的更好方法
- 何时提供默认参数作为模板参数
- 是默认情况下分配给char数组常量的值
- 使用QProcess执行命令,并将结果存储在QStringList中
- 具有默认值的引用获取函数
- 为什么std::atomic的默认构造函数不默认初始化底层存储值
- 默认缓存何时存储在 Apache Ignite 2.0 中的堆外
- 存储具有许多变体的类的默认值的推荐方法是什么?
- 如何将用户输入存储在变量中,该变量与默认构造函数中的变量初始化中的值不同
- Howto在QString中存储HTML代码时,默认浏览器中打开HTML页面
- 在编译时存储默认值的优雅而有效的方法
- 为什么在映射中存储时需要默认构造函数