为什么成员在此示例中未进行零初始化?

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::ib.B::A将值初始化,这会导致这种行为,但被标记为"(直到C++ 11)".

小智 7

对于任何类,如果存在单个用户定义的构造函数,则必须使用它,并且A(){}不进行初始化i.


Fla*_*ire 6

我也会使用编译器错误.

  • 我想我们都同意b得到价值初始化(8.5.4)
  • 运用

    值初始化类型T的对象是指:
    -如果T是一个(可能CV修饰)非工会类型没有用户提供的构造函数,则该对象是零初始化,如果T的隐式声明的默认的构造是非平凡的,那个构造函数被调用.

    所以应该首先进行零初始化,然后调用默认ctors

  • 并定义:

    零初始化T类型的对象或引用意味着:
    - 如果T是(可能是cv限定的)非联合类类型,则每个非静态数据成员和每个基类子对象都是零初始化初始化填充为零位;

因此,应该发生以下情况:

  1. 填充sizeof(B)
  2. 调用A没有任何作用的子对象的构造函数.

我认为这是优化中的一个错误.比较器的输出-O0-O1:https://godbolt.org/z/20QBoR.没有优化,行为是正确的.另一方面,Clang在以下两个方面都是正确的:https://godbolt.org/z/7uhlIi

GCC中的新标准标志仍然存在这个"错误":https://godbolt.org/z/ivkE5K

但是我假设在C++ 20中B是一个"聚合",所以行为变得标准.