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

时间:2019-01-03 19:47:11

标签: c++ c++11 initialization language-lawyer 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;
}

使用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::AB当然不是集合,因为如果我们尝试使用集合初始化,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::ib.B::A都将被值初始化,这将导致这种现象,但是标记为“(直到C ++ 11)”

3 个答案:

答案 0 :(得分:7)

对于任何类,如果只有一个用户定义的构造函数,则必须使用它,并且A(){}不会初始化i

答案 1 :(得分:5)

I'd also go with compiler bug.

  • I think we can all agree that 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

  • And with the definition:

    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:

  1. Fill sizeof(B) with zeroes
  2. Call constructor of subobject A 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