默认构造函数是否始终初始化所有成员
Does a default constructor always initialize all members?
我可以发誓我不记得以前见过这个,而且我很难相信自己的眼睛:
非聚合类的隐式定义的默认构造函数是否初始化其成员?
在 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
的值初始化没有调用任何构造函数,而是导致其a
和b
成员的值初始化。然后,该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。
- C++成员初始化
- c++构造函数成员初始化:传递参数
- C++正确的指针成员初始化
- 将另一个类的对象传递到当前类C++的构造函数中(不是成员初始化)
- WinLamb 错误:成员初始化非法
- 使用其他成员初始化结构的成员?
- C++模板类静态成员初始化
- 解释了构造函数成员初始化列表
- 如何在成员初始化列表中声明共享指针
- C++入门5版:使用get成员初始化另一个与shared_ptr无关的对象
- C++11 默认类成员初始化与初始值设定项列表同时
- 调用非默认构造函数作为成员初始化
- C++模板成员初始化:用右值移动构造,但用左值移动引用
- 类成员初始化C++
- 在成员初始化列表中,我可以创建对列表中不在列表中的成员变量的引用
- C :(不重复)积分静态成员初始化(不仅是声明!),导致链接器错误,原因
- 如何调用成员初始化器列表中参考成员的构造函数
- C 构造函数采用成员初始化器
- 与其他静态const成员初始化静态常量成员
- 静态内联成员初始化顺序