我可以发誓,我不记得以前见过这个,而且我很难相信自己的眼睛:
非聚合类的隐式定义的默认构造函数是初始化其成员还是没有?
在Visual C ++中,当我运行这个看似无辜的代码时......
#include <string>
struct S { int a; std::string b; };
int main() { return S().a; }
...令我惊讶的是,它返回一个非零值!但是如果我删除字段b
,则返回零。
我已经在VC ++的所有版本上尝试了这一点,我可以亲自动手,而且似乎在所有这些版本上都会这样做。
但是当我在Clang和GCC上尝试时,无论我是在C ++ 98模式还是在C ++ 11模式下尝试,都会将值初始化为零。
正确的行为是什么?它不能保证为零吗?
答案 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
的值初始化未调用任何构造函数,但导致其S
和a
成员的值初始化。然后,该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。