c++ - 为什么在此示例中成员未进行零初始化?
问题描述
这是专门针对 C++11 的:
#include <iostream>
struct A {
A(){}
int i;
};
struct B : public A {
int j;
};
int main() {
B b = {};
std::cout << b.i << b.j << std::endl;
}
使用 g++ 8.2.1 编译:
$ g++ -std=c++11 -pedantic-errors -Wuninitialized -O2 a.cpp
a.cpp: In function ‘int main()’:
a.cpp:25:25: warning: ‘b.B::<anonymous>.A::i’ is used uninitialized in this function [-Wuninitialized]
std::cout << b.i << " " << b.j << std::endl
gcc 被检测b.i
为未初始化,但我认为它应该与b.j
.
我认为正在发生的事情(特别是 C++11,来自 ISO/IEC 工作草案 N3337,强调我的):
B
不是聚合,因为它有一个基类。公共基类只允许在 C++17 的聚合中使用。A
不是聚合,因为它具有用户提供的构造函数
第 8.5.1 节
聚合是一个数组或一个类(第 9 条),没有用户提供的构造函数(12.1),没有非静态数据成员的大括号或等号初始值设定项(9.2),没有私有或受保护的非静态数据成员(第9 条) 11), 没有基类(第 10 条),也没有虚函数(10.3)。
b
正在使用空的括号初始化列表初始化列表
第 8.5.4 节
T 类型的对象或引用的列表初始化定义如下:
—如果初始化列表没有元素并且 T 是具有默认构造函数的类类型,则该对象是值初始化的。
— 否则,如果 T 是一个聚合,则执行聚合初始化 (8.5.1)。
- 这意味着
b
获得值初始化 B
有一个隐式定义的默认构造函数,所以b
值初始化调用零初始化b.B::A
被零初始化,它被零初始化,b.B::A.i
然后b.B::j
被零初始化。
第 8.5 节
对 T 类型的对象或引用进行零初始化意味着:
...
— 如果 T 是(可能是 cv 限定的)非联合类类型,则每个非静态数据成员和每个基类子对象都被零初始化,并且填充被初始化为零位;
...
对 T 类型的对象进行值初始化意味着:
— 如果 T 是具有用户提供的构造函数(12.1)的(可能是 cv 限定的)类类型(第 9 条),则调用 T 的默认构造函数(并且初始化如果 T 没有可访问的默认构造函数,则格式错误);
—如果 T 是(可能是 cv 限定的)非联合类类型,没有用户提供的构造函数,则该对象为零初始化,并且如果 T 的隐式声明的默认构造函数不平凡,则调用该构造函数。
但是,看起来 gcc 只是在说只会b.B::j
进行零初始化。为什么是这样?
我能想到的一个原因是如果B
被视为一个聚合,它将b.B::A
使用一个空列表进行初始化。
B
但是,肯定不是聚合,因为如果我们尝试使用聚合初始化,gcc 会正确地出错。
// ... as in the above example
int main() {
B b = {A{}, 1};
std::cout << b.i << " " << b.j << std::endl;
}
使用 C++11 编译
$ g++ -std=c++11 -pedantic-errors -Wuninitialized -O2 a.cpp
a.cpp: In function ‘int main()’:
a.cpp:10:18: error: could not convert ‘{A(), 1}’ from ‘<brace-enclosed initializer list>’ to ‘B’
B b = {A{}, 1};
使用 C++17 编译
g++ -std=c++17 -pedantic-errors -Wuninitialized -O2 a.cpp
a.cpp: In function ‘int main()’:
a.cpp:11:25: warning: ‘b.B::<anonymous>.A::i’ is used uninitialized in this function [-Wuninitialized]
std::cout << b.i << " " << b.j << std::endl;
我们可以看到它b.i
是未初始化的,因为B
它是一个聚合,并且b.B::A
被一个本身未初始化的表达式A::i
初始化。
所以它不是一个集合。另一个原因是 ifb.B::j
正在被零初始化,并且b.B::A
正在被值初始化,但我在规范中的任何地方都没有看到。
最后一个原因是是否调用了旧版本的标准。从cppreference:
2) 如果 T 是没有任何用户提供的构造函数的非联合类类型,则 T 的每个非静态数据成员和基类组件都是值初始化的;(直到 C++11)
在这种情况下,b.B::i
和b.B::A
都将被值初始化,这将导致这种行为,但这被标记为"(直到 C++11)"。
解决方案
对于任何类,如果有一个用户定义的构造函数,则必须使用它,并且A(){}
不初始化i
。
推荐阅读
- angularjs - AngularJS 控制器在 ng-Route 期间未正确加载,否则()
- amazon-web-services - aws golang sdk 进行异步创建实例调用
- openlayers - OpenLayers 6 - 添加第二个矢量切片图层会导致基础图层标签消失
- c# - 无法从“对象”转换为“MySql.Data.MySqlClient.MySqlDbType”
- c++ - *--end() 在 C++ 中是什么意思?
- javascript - TS/JS 自启动类
- c# - 带有asp.net网页表单页面代码的扩展方法存在问题
- list - 在列表中的元素之间添加零?
- python - 在 Linux Mint 中使用启动器和基本代码启动 Jupyter Notebook
- windows - 如何诊断“随机”winsock 故障