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

标签 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 不是聚合,因为它有一个基类。公共(public)基类只允许在 C++17 的聚合中使用。
  • A 不是聚合,因为它有一个用户提供的构造函数

第 8.5.1 节

An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no brace-or-equal initializers for non-static data members (9.2), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).

  • b 正在使用空的花括号初始化列表初始化列表

第 8.5.4 节

List-initialization of an object or reference of type T is defined as follows:
If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
— Otherwise, if T is an aggregate, aggregate initialization is performed (8.5.1).

  • 这意味着 b 得到了值初始化
  • B 有一个隐式定义的默认构造函数,所以 b 值初始化调用零初始化
  • b.B::A 被零初始化,它对 b.B::A.i 进行零初始化,然后 b.B::j 被归零-初始化。

第 8.5 节

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;

...

To value-initialize an object of type T means:
— if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
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.

但是,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) if T is a non-union class type without any user-provided constructors, every non-static data member and base-class component of T is value-initialized; (until C++11)

在这种情况下,b.B::ib.B::A 都将进行值初始化,这将导致此行为,但标记为 "(C++11 前)".

最佳答案

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

关于c++ - 为什么在此示例中成员未进行零初始化?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54028846/

相关文章:

c++ - 如何拥有灵活的嵌套初始化器?

c++ - 函数和方法的放置和使用

c++ - 运算符重载

c++ - 使用 esp32 的 AWS iot 接收来自某些主题的消息,但不接收其他主题的消息?

c++ - 抛出派生自不可复制的可复制类

ios - 如何在 IBAction 按钮中初始化 Tile 源一次

c++ - 确定删除并发队列的安全性

c++ - 如何在 C++ 中以毫秒为单位输出时间?

opengl - 如何将 int 转换为 const GLvoid*?

java - 特殊的 Java 作用域