【问题标题】:Does the C++ standard allow zero-initialization of a POD object with const members?C++ 标准是否允许对具有 const 成员的 POD 对象进行零初始化?
【发布时间】:2014-05-15 23:50:17
【问题描述】:

我已经定义了一个 POD,打算将其用作不可变数据存储。为此,我将其成员限定为const,并期望对实例进行值初始化(在某些情况下进行零初始化)。考虑以下代码:

struct Foo
{
    const int value;
};

int main()
{
    Foo foo{ };

    return 0;
}

当我尝试对此 POD 进行零初始化时,由于 Foo::value 上的 const 限定符,我在 Visual Studio (C3852) 中收到编译器错误。如果我删除限定符,代码编译得很好。

确切的错误信息是:

错误 C3852:“Foo::value”类型为“const int”:聚合初始化无法初始化此成员 const 成员不能被默认初始化,除非它们的类型有用户定义的默认构造函数

根据标准 (draft n3337),§8.5/5(零初始化):

对 T 类型的对象或引用进行零初始化意味着:

—如果 T 是标量类型 (3.9),则将对象设置为值 0(零),作为整数常量表达式, 转换为 T;

——如果 T 是(可能是 cv 限定的)非联合类类型,则每个非静态数据成员和每个基类 子对象初始化为零,填充初始化为零位;

——如果 T 是一个(可能是 cv 限定的)联合类型,则对象的第一个非静态命名数据成员被零初始化 并且填充被初始化为零位;

——如果T是数组类型,每个元素都初始化为零;

——如果 T 是引用类型,则不执行初始化。

和§8.5/6(默认初始化):

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

——如果 T 是一个(可能是 cv 限定的)类类型(第 9 条),则调用 T 的默认构造函数(并且 如果 T 没有可访问的默认构造函数,则初始化是非良构的);

——如果 T 是一个数组类型,每个元素都是默认初始化的;

——否则,不执行初始化。 如果程序要求对 const 限定类型 T 的对象进行默认初始化,则 T 应是一个类 使用用户提供的默认构造函数键入。

和§8.5/7(值初始化):

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

——如果 T 是一个(可能是 cv 限定的)类类型(第 9 条),带有一个用户提供的构造函数(12.1),那么 调用 T 的默认构造函数(如果 T 没有可访问的默认值,则初始化格式错误 构造函数);

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

——如果T是一个数组类型,那么每个元素都是值初始化的;

——否则,对象被零初始化。

我对标准的阅读使我相信我的 POD 应该是零初始化的;未默认初始化。我是否误解了标准中描述的初始化过程?

编辑:考虑到接受的答案和相关 cmets 中提供的详细信息,这看起来像是 VS 实现中的潜在错误(即,实现可能基于标准的过时版本) .我创建了一个 Microsoft Connect 票证来跟踪它,可以在这里找到:

https://connect.microsoft.com/VisualStudio/feedback/details/846222/c-compiler-uses-incorrect-initialization-scheme-for-certain-objects

【问题讨论】:

  • 为什么有 const 成员?为什么不只是一个 const Foo?
  • 您没有使用 Foo foo{ }; 语法进行零初始化。这是聚合初始化,初始化器比成员少,这意味着数据成员将从空的{} 初始化。
  • @BenVoigt AFAIK {} 始终是 list-init,并且至少在以后的草案 (>= n3485) 中,聚合 init 是 list-init 的第一个可能选项。不过,n3337 在 list-init 中的 aggregate-init 之前仍然有 value-init。看CWG DR 1301的分辨率
  • @Lilshieste 我猜这与 CWG1301 之前/之后有关。
  • 呃,问题中的规则也改变了该草案和 C++11 之间的措辞显着

标签: c++ c++11 language-lawyer


【解决方案1】:

@dyp 关于聚合初始化发生的评论是正确的,但会导致成员的零初始化。

Foo foo{ };

是列表初始化,所以我们从8.5.4开始。 8.5.4p3 说(从草案 n3690 订购,与 C++11 匹配,仍在 n3797 中)

类型 T 的对象或引用的列表初始化定义如下:

  • 如果 T 是聚合,则执行聚合初始化 (8.5.1)。
  • 否则,如果初始值设定项列表没有元素且T 是具有默认构造函数的类类型,则该对象是值初始化的。
  • 否则,如果Tstd::initializer_list<E>的特化,则按如下所述构造prvalueinitializer_list对象,并用于根据从相同类型的类中初始化对象的规则来初始化对象(8.5)。
  • 否则,如果T 是类类型,则考虑构造函数。枚举适用的构造函数 并且通过重载决议(13.3、13.3.1.7)选择最好的一个。如果缩小转换(见 下面) 需要转换任何参数,程序格式错误。
  • 否则,如果初始化列表有一个 E 类型的元素,并且 T 不是引用类型或其引用类型与 E 引用相关,则从该元素初始化对象或引用;如果需要缩小转换(见下文)将元素转换为T,则程序格式错误。
  • 否则,如果T 是引用类型,则T 引用的类型的prvalue 临时是复制列表初始化或直接列表初始化,具体取决于引用的初始化类型,并且引用绑定到该临时文件。
  • 否则,如果初始化器列表没有元素,则对象被值初始化。
  • 否则,程序格式错误。

在第一种情况下,我们必须访问 8.5.1 进行类的聚合初始化。 p7:

如果列表中的初始化子句少于聚合中的成员,则每个未显式初始化的成员都应从其大括号或相等初始化器中初始化,或者,如果没有大括号或相等-initializer,来自一个空的初始化列表 (8.5.4)。

所以value 成员从一个空的初始化器列表中进行列表初始化,它递归到上述规则,到达倒数第二个情况,即值初始化。当然,从问题的规则来看,那就是零初始化。


dyp 提到在实现 CWG 1301 的 n3485 之前,默认构造规则优先,并且会尝试访问已删除的默认构造函数但失败。

【讨论】:

  • 我认为重要的是要补充一点,前两个项目符号点已经颠倒(在 n3337 和 n3485 之间)可能是因为 CWG 1301 如果 MSVC 没有实现,代码被拒绝,因为 @987654338 @ 确实是对 Foo 对象进行值初始化;这会导致调用已删除的默认 ctor [class.ctor]/5。
  • @dyp:isocpp.org 似乎没有比我引用的 n3797 更新的草案?
  • @Lilshieste:如果您打开公共票,请在此处链接 Connect 票,以便我们投票。 (并且您可以在您对 Connect 的解释中链接到 SO 问题,MS C++ 编译器团队似乎喜欢知道很多人已经看过这个问题并同意这是一个错误。)
  • @Lilshieste:TY,添加了我的投票。顺便说一句,根据错误消息,CWG 1301 更改不是这里的问题。编译器正在尝试聚合初始化。但它不尊重我在回答中引用的 8.5.1p7,它需要成员的列表初始化。编译器改为使用默认初始化。
猜你喜欢
  • 2015-08-08
  • 1970-01-01
  • 2020-05-28
  • 1970-01-01
  • 1970-01-01
  • 2019-02-04
  • 2014-01-17
  • 2014-12-31
  • 1970-01-01
相关资源
最近更新 更多