默认构造函数是否始终初始化所有成员

Does a default constructor always initialize all members?

本文关键字:成员 初始化 构造函数 是否 默认      更新时间:2023-10-16

我可以发誓我不记得以前见过这个,而且我很难相信自己的眼睛:

非聚合类的隐式定义的默认构造函数是否初始化其成员?

在 Visual C++ 中,当我运行这个看起来无辜的代码时......

#include <string>
struct S { int a; std::string b; };
int main() { return S().a; }

。令我惊讶的是,它返回了一个非零值!但是如果我删除字段b,那么它返回零。

我已经在我能得到的所有版本的 VC++ 上尝试过这个,它似乎在所有版本上都这样做了。

但是当我在 Clang 和 GCC 上尝试时,无论我在 C++98 模式还是 C++11 模式下尝试,值都初始化为零。

什么是正确的行为?不是保证为零吗?

引用

C++11:

5.2.3 显式类型转换(函数表示法) [expr.type.conv]

2 表达式 T() ,其中 T 是非数组完整对象类型或(可能符合 cv 的)void 类型的简单类型说明符或类型名说明符,创建指定类型的 prvalue 值,该值初始化(8.5;对于void()情况不进行初始化)。[...]

8.5 初始值设定项 [dcl.init]

7 对类型T的对象进行值初始化意味着:

  • 如果 T 是没有用户提供的构造函数的(可能符合 CV 条件的)非联合类类型,则该对象为零初始化,如果 T 隐式声明的默认构造函数是非平凡的,则调用该构造函数。

因此,在 C++11 中,S().a 应为零:对象在调用构造函数之前初始化为零,并且构造函数永远不会将 a 的值更改为其他任何值。

在 C++11 之前,值初始化具有不同的描述。引用N1577(大约C++03):

对 T 类型的对象进行值初始化意味着:

  • 如果 T 是没有用户声明构造函数的非联合类类型,则 T 的每个非静态数据成员和基类组件都是值初始化的;
  • 否则,对象初始化为零

在这里,S的值初始化没有调用任何构造函数,而是导致其ab成员的值初始化。然后,该a成员的值初始化导致该特定成员的零初始化。在 C++03 中,结果也保证为零。

甚至更早,进入第一个标准,C++98:

表达式 T() ,其中 T 是非数组完整对象类型或(可能符合 cv 的)void类型的简单类型说明符 (7.1.5.2),创建指定类型的 rvalue,其值由默认初始化确定(8.5;对于void()情况不进行初始化)。

默认初始化类型为 T 的对象意味着:

  • 如果T是非 POD 类类型(子句 9),则调用 T 的默认构造函数(如果T没有可访问的默认构造函数,则初始化格式不正确);
  • 否则,对象的存储初始化为零。

因此,基于第一个标准,VC++ 是正确的:当您添加 std::string 成员时,S成为非 POD 类型,并且非 POD 类型不会获得零初始化,它们只是调用了它们的构造函数。隐式生成的 S 默认构造函数不会初始化a成员。

所以所有的编译器都可以说是正确的,只是遵循不同版本的标准。

正如@Columbo在评论中报告的那样,更高版本的VC++确实会导致a成员根据C++标准的最新版本进行初始化。

(第一部分的所有引文均来自N3337,C++11 FD,编辑更改)

我无法在 rextest 上使用 VC++ 重现该行为。据推测,该错误(见下文)已经在他们使用的版本中修复,但在您的版本中未修复 - @Drop报告说最新版本VS 2013 Update 4未能通过断言 - 而VS 2015预览版通过了它们。

只是为了避免误解:S确实是一个聚合体。[dcl.init.aggr]/1:

聚合是没有用户提供的数组或类(第 9 条) 构造函数 (12.1),没有私有或受保护的非静态数据成员 (条款 11),没有基类(条款 10),也没有虚函数 (10.3).

不过,这无关紧要。
值初始化的语义很重要。[dcl.init]/11:

一个对象,其初始值设定项是一组空的括号,即 () ,应进行值初始化。

[dcl.init]/8:

对类型为 T 的对象进行值初始化意味着:

  • 如果T是(可能符合 cv 条件的)类类型(第 9 条),没有默认构造函数 (12.1) 或 默认构造函数是用户提供或删除的,则对象是默认初始化的;
  • 如果 T 是一个(可能符合 CV 条件的)类类型,没有用户提供或删除的默认构造函数,则对象为零初始化并检查默认初始化的语义约束,如果T具有非平凡的默认构造函数,则对象默认初始化;
  • [..]

显然,无论b是否S,这都是成立的。因此,至少在 C++11 中,在这两种情况下,a都应该为零。Clang和GCC显示正确的行为。


现在让我们来看看 C++03 FD:

对类型为 T 的对象进行值初始化意味着:

  • 如果T是具有用户声明的构造函数 (12.1) 的类类型(子句 9)[..]
  • 如果 T 是没有用户声明构造函数的非联合类类型,则每个非静态数据成员和基类 T的组件是值初始化的;
  • 如果T是数组类型,则每个元素都是值初始化的;
  • 否则,对象初始化为零

也就是说,即使在 C++03 中([dcl.init]/11 中的上述引用也存在于/7 中),a在这两种情况下都应该0
同样,GCC 和 Clang 在 -std=c++03 中都是正确的。

如 hvd 的答案所示,您的版本仅符合 C++98 和 C++98。