【问题标题】:Does a default constructor always initialize all members?默认构造函数是否总是初始化所有成员?
【发布时间】:2015-02-24 10:09:40
【问题描述】:

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

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

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

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

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

我已经在我能接触到的所有 VC++ 版本上尝试过这个,而且似乎在所有版本上都可以做到这一点。

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

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

【问题讨论】:

  • 具体是哪个版本的VC++?目前还没有完全支持 C++11 的版本,所以这真的很重要。
  • 固定在Visual Studio 2015 Preview。这是将 Windows 项目转移到它的另一个好理由(以及constexpr 以及 android clang 和 gdb 集成)

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


【解决方案1】:

引用 C++11:

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

2 表达式T(),其中T 是非数组完整对象类型的simple-type-specifiertypename-specifier 或(可能是 cv 限定的)void 类型,创建指定类型的纯右值,它是值初始化的(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 限定的)@987654339 @type,创建指定类型的右值,其值由默认初始化确定(8.5;void() 情况下不进行初始化)。

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

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

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

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

正如@Columbo 在 cmets 中所报告的,更高版本的 VC++ 确实会根据最新版本的 C++ 标准导致 a 成员被初始化。

【讨论】:

  • 似乎 8.5(10) 会导致 8.5(7) 并最终导致 8.5(5) 并且由于 a 确实是第一个非静态成员,我看不出它怎么会不会进行值初始化。我认为你是对的;在() 的情况下应该是零。 +1。我没有 14-std,但如果 Columbo 的引用是准确的,那么似乎 8.5(10) 已移至 8.5(11)。
  • 哈哈,几分钟前我还想过为 C++03 提供相同的论证。我没有偷,我保证。 :D
  • @hvd 这绝对是搞笑的,最新版本的 VC++ 预计至少会遵循 C++03(十多年前推出)。我会说这需要一个错误报告。
  • @Columbo 看起来 rextester.com 使用的不是最新的 MSVC 稳定版本。最新版本 Visual Studio 2013 Update 4(以及任何比它更早的版本)失败 asserts。 Visual Studio 2015 Preview 通行证。
  • @Mehrdad 是的,差不多。 T() 旨在生成与 static T val; // no initializer 将被初始化的值相同的值,而原始 C++98 标准中的措辞没有这种效果。
【解决方案2】:

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

我无法重现 VC++ on rextester 的行为。据推测,该错误(见下文)已在他们使用的版本中修复,但在您的版本中未修复 - @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 and GCC show the correct behavior.


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

值初始化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。

【讨论】:

  • 现在参考您正在使用的实际标准。有几个可供选择。
  • 这缺少一个非常相关的细节:当b 字段被删除时,S().a 为零。您的回答尚未解释 b 字段有什么不同。或者你是说这同样是未定义的,而零也是同样有效的结果? (我不认为这是正确的,我不认为这就是你想说的。)
  • @hvd 啊,我没看到它是 S()!我会尽快更正答案。
  • 嘿,我也刚刚发布了,在您编辑答案几秒钟后。 :) 不过,我不会删除我的答案。我认为这是后来的标准改变了规则的领域,如果 VC++ 遵循旧的标准,它的行为可能仍然是正确的。
猜你喜欢
  • 2015-01-12
  • 2011-04-08
  • 2015-10-07
  • 1970-01-01
  • 2011-01-25
  • 2011-08-09
  • 1970-01-01
相关资源
最近更新 更多