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

时间:2014-12-27 14:39:13

标签: c++ visual-c++ constructor initialization default-constructor

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

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

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

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

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

我已经在VC ++的所有版本上尝试了这一点,我可以亲自动手,而且似乎在所有这些版本上都会这样做。

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

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

2 个答案:

答案 0 :(得分:14)

引用C ++ 11:

  

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

     

2表达式{​​{1}},其中T()简单类型说明符 typename-specifier ,用于非数组完整对象type或(可能是cv-qualified)T类型,创建一个指定类型的prvalue,它是值初始化的(8.5;没有为void情况进行初始化)。 [...]

     

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

     

7要值初始化类型为void()的对象,意味着:

     
      
  • ...
  •   
  • 如果T是一个(可能是cv限定的)非联合类类型而没有用户提供的构造函数,那么该对象是零初始化的,如果T隐式声明的默认值构造函数是非平凡的,该构造函数被调用。
  •   
  • ...
  •   

所以在C ++ 11中,T应该为零:在调用构造函数之前对象是零初始化的,构造函数永远不会将S().a的值更改为其他任何内容。

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

  

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

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

此处,T的值初始化未调用任何构造函数,但导致其Sa成员的值初始化。然后,该b成员的值初始化导致该特定成员的初始化为零。在C ++ 03中,结果也保证为零。

甚至更早,转到第一个标准,C ++ 98:

  

表达式a,其中T()简单类型说明符(7.1.5.2),用于非数组完整对象类型或(可能是cv-限定的T类型,创建一个指定类型的右值,其值由default-initialization确定(8.5;没有对void情况进行初始化)。

  

默认初始化 void()类型的对象意味着:

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

因此,基于第一个标准,VC ++是正确的:当您添加T成员时,std::string变为非POD类型,而非POD类型不会初始化为零,他们只是打电话给他们的构造函数。隐式生成的S默认构造函数不会初始化S成员。

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

正如@Columbo在评论中所报告的那样,VC ++的更高版本确实会导致a成员被初始化,这符合更新版本的C ++标准。

答案 1 :(得分:11)

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

我无法使用VC++ on rextester重现该行为。据推测,这个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:

  

value-initialize T类型的对象意味着:

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

显然,无论b是否在S中,这都有效。因此,至少在C ++ 11中,a两种情况都应为零。 Clang and GCC show the correct behavior


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

  

value-initialize T类型的对象意味着:

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

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

hvd's answer所示,您的版本仅适用于C ++ 98和C ++ 98。