Mik*_*Lui 20 c++ initialization language-lawyer c++11 list-initialization
这特别是关于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;
}
Run Code Online (Sandbox Code Playgroud)
用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
Run Code Online (Sandbox Code Playgroud)
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
进行零初始化.为什么是这样?
我能想到的一个原因是if 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;
}
Run Code Online (Sandbox Code Playgroud)
用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};
Run Code Online (Sandbox Code Playgroud)
用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;
Run Code Online (Sandbox Code Playgroud)
我们可以看到它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)运用
值初始化类型T的对象是指:
-如果T是一个(可能CV修饰)非工会类型没有用户提供的构造函数,则该对象是零初始化和,如果T的隐式声明的默认的构造是非平凡的,那个构造函数被调用.
所以应该首先进行零初始化,然后调用默认ctors
零初始化T类型的对象或引用意味着:
- 如果T是(可能是cv限定的)非联合类类型,则每个非静态数据成员和每个基类子对象都是零初始化并初始化填充为零位;
因此,应该发生以下情况:
sizeof(B)
零A
没有任何作用的子对象的构造函数.我认为这是优化中的一个错误.比较器的输出-O0
到-O1
:https://godbolt.org/z/20QBoR.没有优化,行为是正确的.另一方面,Clang在以下两个方面都是正确的:https://godbolt.org/z/7uhlIi
GCC中的新标准标志仍然存在这个"错误":https://godbolt.org/z/ivkE5K
但是我假设在C++ 20中B
是一个"聚合",所以行为变得标准.