为什么成员在此示例中未进行零初始化?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么成员在此示例中未进行零初始化?相关的知识,希望对你有一定的参考价值。
这特别是关于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)没有大括号或相等的初始值,没有私有或受保护的非静态数据成员(子句) 11),没有基类(第10条),也没有虚函数(10.3)。
b
正在使用空的braced-init-list初始化列表
第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
未初始化。
所以它不是聚合。另一个原因是如果b.B::j
被零初始化,并且b.B::A
正在进行价值初始化,但我没有在规格中看到任何地方。
最后一个原因是如果调用旧版本的标准。来自cppreference:
2)如果T是不带任何用户提供的构造函数的非联合类类型,则T的每个非静态数据成员和基类组件都是值初始化的; (直到C ++ 11)
在这种情况下,b.B::i
和b.B::A
都将进行值初始化,这会导致此行为,但标记为“(直到C ++ 11)”。
我也会使用编译器错误。
- 我想我们都同意
b
得到价值初始化(8.5.4) - 运用 value-initialize类型为T的对象意味着: - 如果T是一个(可能是cv限定的)非联合类类型而没有用户提供的构造函数,那么该对象是零初始化的,如果T的隐式声明的默认构造函数是非平凡的,则调用该构造函数。 所以应该首先进行零初始化,然后调用默认ctors
- 并定义: 零初始化T类型的对象或引用意味着: - 如果T是(可能是cv限定的)非联合类类型,则每个非静态数据成员和每个基类子对象都是零初始化的,并且填充初始化为零位;
因此,应该发生以下情况:
- 用零填充
sizeof(B)
- 调用没有任何作用的子对象
A
的构造函数。
我认为这是优化中的一个错误。将-O0
的输出与-O1
:https://godbolt.org/z/20QBoR进行比较。没有优化,行为是正确的。另一方面,Clang在两个方面都是正确的:https://godbolt.org/z/7uhlIi
这个“bug”仍然存在于GCC中的新标准标志:https://godbolt.org/z/ivkE5K
但是我假设在C ++ 20中B
是一个“聚合”,所以行为成为标准。
对于任何类,如果有一个用户定义的构造函数,则必须使用它,并且A(){}
不会初始化i
。
什么都没有初始化i
。它不会自动发生。您需要在类中或在类构造函数的初始化列表中初始化它。或者删除你的非平凡/用户定义的构造函数(或= default
它,这使它变得微不足道)。
编译器正在使用您提供的构造函数,并且ctor没有初始化i
。
以上是关于为什么成员在此示例中未进行零初始化?的主要内容,如果未能解决你的问题,请参考以下文章