【问题标题】:Zero-Initialize array member in initialization list初始化列表中的零初始化数组成员
【发布时间】:2015-02-07 12:39:51
【问题描述】:

我有一个包含数组成员的类,我想将其初始化为全零。

class X
{
private:
    int m_array[10];
};

对于局部变量,有一种简单的零初始化方法(参见here):

int myArray[10] = {};

此外,类成员 m_array 显然需要初始化,因为默认初始化 int 只会留下随机垃圾,正如 here 解释的那样。

但是,我可以看到对成员数组执行此操作的两种方法:

带括号:

public:
    X()
    : m_array()
    {}

带大括号:

public:
    X()
    : m_array{}
    {}

都正确吗?两者在 C++11 中有什么区别吗?

【问题讨论】:

  • 你是如何测试的?使用gcc -std=c++98 编译时,我收到一条消息“警告:扩展初始化列表仅适用于 -std=c++11 或 -std=gnu++11 [默认启用]”,甚至无需添加 -pedantic .
  • 我刚刚用 C++ 4.8.1 选项快速检查了ideone.com,我不知道他们使用了什么标志,所以我删除了我的声明。
  • Visual Studio 2013 为第二个选项输出spurious 警告消息:warning C4351: new behavior: elements of array 'X::m_array' will be default initialized

标签: c++ initialization initialization-list array-initialization


【解决方案1】:

使用() 初始化任何成员都会执行值初始化。

使用带有{} 的默认构造函数初始化任何类类型会执行值初始化。

使用{} 初始化任何其他聚合类型(包括数组)执行列表初始化,相当于使用{} 初始化聚合的每个成员。

使用{} 初始化任何引用类型会构造一个从{} 初始化的临时对象,并将引用绑定到该临时对象。

使用{} 初始化任何其他类型会执行值初始化。

因此,对于几乎所有类型,从 {} 进行的初始化将给出与值初始化相同的结果。你不能有引用数组,所以这些不能是例外。您可能能够在没有默认构造函数的情况下构造聚合类类型的数组,但编译器在确切规则上并不一致。但回到您的问题,所有这些极端情况对您来说并不重要:对于您的特定数组元素类型,它们具有完全相同的效果。

【讨论】:

    【解决方案2】:

    初始化的类型可能有点乏味,但在这种情况下它是微不足道的。对于:

    public:
        X()
        : m_array()
        {}
    

    由于括号之间的表达式列表为空,因此发生值初始化。类似的:

    public:
        X()
        : m_array{}
        {}
    

    list-initialization 发生,随后 value-initialization 因为 brace-init-list 为空。


    为了给出更全面的答案,让我们看一下 N4140 的 §8.5。

    1. 如果没有为对象指定初始化器,则该对象为 默认初始化。当使用自动或 动态存储时长,对象有一个indeterminate 值,如果没有对对象进行初始化,那 对象保留一个不确定的值,直到该值被替换 (5.17)。

    这个不确定的值就是你所说的垃圾值。

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

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

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

      ——如果 T 是一个(可能是 cv 限定的)类类型……那么对象是默认初始化的; ...

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

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

    3. 初始化器的语义如下。 ... — 如果初始化器是(非括号)braced-init-list,则对象或引用是列表初始化的(8.5.4)。

      ——如果初始化器是(),则对象是值初始化的。

    到目前为止,很明显值初始化将使数组的每个元素都为零,因为int 不是类类型。但是我们还没有讨论列表初始化和聚合初始化,因为数组是一个聚合。

    §8.5.4:

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

      ——如果 T 是一个聚合,则执行聚合初始化 (8.5.1)。

    回到第 8.5.1 节:

    1. 如果列表中的 initializer-clauses 比列表中的少 是聚合中的成员,那么每个成员都不是明确的 initialized 应该从它的 brace-or-equal-initializer 初始化 或者,如果没有 brace-or-equal-initializer,则从一个空的 初始化列表 (8.5.4)。

    我们再次以 §8.5.4 结束:

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

      ——否则,如果初始化列表没有元素,则对象被值初始化。

    由于遍历(草案)标准会让你喘不过气来,我推荐cppreference,因为它很好地分解了它。

    相关链接:

    cppreference:

    标准草案:

    【讨论】:

      【解决方案3】:

      括号在 C++98 中工作,并要求零初始化,这正是您想要的。我在 gcc 4.3 上进行了验证。编辑:删除了关于 C++11 的错误陈述。我还确认空括号使用带有 -std=c++11 的 clang 3.4 执行空列表初始化。

      【讨论】:

      • 关于 C++11 的部分不正确。 : m_array{} 中的 {} 是一个 braced-init-list(不是 std::initializer_list),很可能是空的。这个空的 braced-init-list 执行数组的值初始化,这会导致 int 元素的零初始化。
      最近更新 更多