结构声明中空括号的含义是什么?

时间:2016-09-10 18:54:59

标签: c++ c++11

以下是struct的简明定义,仅显示问题点。

struct Entry {
    // bookkeeping record for managing solution search
    std::array<std::array<bool, DIM>, DIM> filled;   // which holes have been filled
    std::array<std::array<char, MAX>, MAX> cells;    // individual cell entries, 0=empty
    std::vector<Constraint> overlaps;
    std::vector<Hole*>::iterator candidates;
    Entry() = default;
};

这实际上是一个错误。我当时认为默认构造函数会对数组进行零初始化,但它只是用随机垃圾填充它们。我现在知道我需要编写默认构造函数,但是我对测试时遇到的行为感到困惑。

这是我的测试功能的简化版本:

void testEntry(void) {
    Entry e;
    std::cout << std::boolalpha;
    e.cells[1][2] = 'a';
    e.filled[0][0] = true;
    for (int i = 0; i < MAX; ++i)
        for (int j = 0; j < MAX; ++j)
            std::cout<<i<<" "<<j<<" "<<e.cells[i][j]<<std::endl;
    for (int i = 0; i < DIM; ++i)
        for (int j = 0; j < DIM; ++j)
            std::cout<<i<<" "<<j<<" "<<e.filled[i][j]<<std::endl;
}

当我运行它时,filledcells数组包含随机垃圾,最终我发现了我的错误。在调试时,我将声明Entry e;更改为Entry e{};,修改后的代码似乎按照我的意图运行,但我不明白为什么。

我无法完全遵循文档。在list initialization下,它表示,“如果braced-init-list为空且T是具有默认构造函数的类类型,则执行值初始化。”这很清楚,但是当我去value initialization时,我无法弄清楚哪些情况适用(如果有的话)。 似乎正在应用zero-intialization下的此条款 “如果T是非联合类类型,则所有基类和非静态数据成员都是零初始化的,并且所有填充都初始化为零位。构造函数(如果有的话)被忽略,”但我看不到怎么到这儿。

最重要的是,我想知道这个行为是否实际在规范中定义,或者它是否与不同的编译器不同。 (我在Xcode 7.2.1下使用clang)。

2 个答案:

答案 0 :(得分:6)

  

结构声明中空括号的含义是什么?

T object {};__atomic的语法。没有大括号,对象将是value initialization (4)

要迂腐,这不是&#34;结构声明&#34;。它是变量的声明。

  

当我进行值初始化时,我无法确定哪些情况适用(如果有的话)。

这适用:

  

2)如果T是具有默认构造函数的类类型,该构造函数既不是用户提供也不是删除(也就是说,它可能是具有隐式定义或默认默认构造函数的类),该对象是零初始化的并且如果它有一个非平凡的默认构造函数,那么它是默认初始化的; (自C ++ 11起)

这就是我们如何实现零初始化。您对零初始化规则的解释是正确的。

  

我想知道这种行为是否实际上是在规范中定义的

链接页面中的规则基于标准,而不是基于特定实现。行为已定义。

答案 1 :(得分:1)

标准中的措辞在C ++ 11和C ++ 14之间发生了变化,最终结果相似但不完全相同。

此代码是 list-initialization ,因为支持列表(尽管为空)是初始化程序。在C ++ 11中,[dcl.init.list] / 3的以下引用适用:

  

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

     
      
  • 如果初始化列表没有元素且T是具有默认构造函数的类类型,则该对象是值初始化的。
  •   

然而,在C ++ 14中,同一部分以不同的要点开头:

  

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

     
      
  • 如果T是聚合,则执行聚合初始化
  •   

在这种情况下,Entry是一个聚合,因为它符合一个条件([dcl.init.aggr] / 1):

  

聚合是一个数组或没有用户提供的构造函数的类(12.1),没有私有或受保护的非静态数据成员,没有基类,也没有虚函数

“用户提供”一词不包括默认功能。

聚合初始化的行为在[dcl.init.aggr]中有所说明,它表示列表的连续成员被视为聚合成员的初始化者。当列表成员少于聚合成员时,[dcl.init.aggr] / 7适用:

  

如果列表中的initializer-clause少于聚合中的成员,那么未明确初始化的每个成员都应从其 brace-or-equal-initializer 初始化,如果有的话是没有支撑或平等 -   初始值设定项,来自空的初始化列表。

这就是说,例如,e.overlaps的初始化方式与std::vector<Constraint> overlaps{};相同。

您可能会注意到这是聚合的递归定义。当我们得到一个不是聚合的东西(例如std::vector)时,[dcl.init.list] / 3中的第二个项目符号将被应用,这是C ++ 11中的第一个项目:值 - 初始化。

比较C ++ 11和C ++ 14,有一点不同:在C ++ 14中,在调用默认构造函数之前,没有零初始化整个对象的步骤成员。相反,只有没有用户提供的构造函数的子对象才会被初始化为零。在C ++ 14中,Entry中的struct padding可能未初始化为零位。 (std::array成员之后可能有填充)。同样,如果std::vector的内部包含任何未由其构造函数初始化的成员;它们将保持未初始化状态,而不是零初始化。