这是专门针对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
聚集是具有没有用户提供的构造函数(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
未初始化的表达式初始化。
所以这不是一个汇总。另一个原因是b.B::j
是否被初始化为零,而b.B::A
是否被初始化为值,但是我在规范中的任何地方都看不到。
最后一个原因是是否调用了旧版本的标准。 来自cppreference:
2)如果T是一个没有用户提供的构造函数的非联合类类型,则T的每个非静态数据成员和基类组件都是值初始化的; (直到C ++ 11)
在这种情况下,b.B::i
和b.B::A
都将被值初始化,这将导致这种现象,但是标记为“(直到C ++ 11)” 。
答案 0 :(得分:7)
对于任何类,如果只有一个用户定义的构造函数,则必须使用它,并且A(){}
不会初始化i
。
答案 1 :(得分:5)
I'd also go with compiler bug.
b
gets value-initialized (8.5.4)Using
value-initialize an object of type T means:
— if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T’s implicitly-declared default constructor is non-trivial, that constructor is called.
So what should happen is first zero-initialization, then default ctors may be called
To zero-initialize an object or reference of type T means:
— if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;
Hence the following should happen:
sizeof(B)
with zeroesA
which does nothing.I assume this is a bug in the optimization. Compare the output of -O0
to -O1
: https://godbolt.org/z/20QBoR. Without optimization the behaviour is correct. Clang on the other hand is correct in both: https://godbolt.org/z/7uhlIi
This "bug" is still present with newer standard flags in GCC: https://godbolt.org/z/ivkE5K
However I assume in C++20 B
is an "aggregate" so the behavior becomes standard.
答案 2 :(得分:0)
什么都没有初始化i
。它不会自动发生。您需要在类中或在类构造函数的初始化列表中对其进行初始化。或删除您的非平凡/用户定义的构造函数(或= default
使其变得平凡)。
编译器正在使用您提供的构造函数,并且ctor不会不初始化i
。