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

Meh*_*dad 15 c++ constructor initialization default-constructor visual-c++

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

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

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

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

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

我已经在VC++的所有版本上尝试了这个,我可以得到它,它似乎在所有这些上都这样做.

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

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

小智 14

引用C++ 11:

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

2表达式T(),其中是非数组完整对象类型或(可能是cv-qualified)类型Tsimple-type-specifiertypename-specifiervoid,它创建一个指定类型的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简单类型说明符(7.1.5.2)或(可能是cv限定的)void类型,创建指定类型的右值,其值由默认初始化确定(8.5;没有对该void()情况进行初始化).

默认初始化的类型的对象T是指:

  • 如果T是非POD类类型(第9节),T则调用默认构造函数(如果T没有可访问的默认构造函数,则初始化是错误的);
  • ...
  • 否则,对象的存储被零初始化.

所以基于第一个标准,VC++是正确的:当你添加一个std::string成员,S变成非POD类型,而非POD类型没有得到零初始化时,他们只是调用它们的构造函数.隐式生成的默认构造函数S不会初始化a成员.

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

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


Col*_*mbo 11

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

我无法在rextester上重现VC++的行为.据推测,这个bug(见下文)已经在他们正在使用的版本中修复,但不在你的版本中 - @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意味着:

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

显然,无论是否b存在,这都是有效的S.所以至少在C++ 11中,两种情况a都应该为零.Clang和GCC显示正确的行为.


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

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

  • if 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.