值初始化:默认初始化还是零初始化?

Mor*_*enn 18 c++ constructor initialization language-lawyer c++14

我有模板化的gray_code类,它用于存储一些无符号整数,其基础位以格雷码顺序存储.这里是:

template<typename UnsignedInt>
struct gray_code
{
    static_assert(std::is_unsigned<UnsignedInt>::value,
                  "gray code only supports built-in unsigned integers");

    // Variable containing the gray code
    UnsignedInt value;

    // Default constructor
    constexpr gray_code()
        = default;

    // Construction from UnsignedInt
    constexpr explicit gray_code(UnsignedInt value):
        value( (value >> 1) ^ value )
    {}

    // Other methods...
};
Run Code Online (Sandbox Code Playgroud)

在一些通用算法中,我写了这样的东西:

template<typename UnsignedInt>
void foo( /* ... */ )
{
    gray_code<UnsignedInt> bar{};
    // Other stuff...
}
Run Code Online (Sandbox Code Playgroud)

在这段代码中,我期望bar零初始化,因此bar.value要进行零初始化.但是,在遇到意外错误之后,似乎bar.value用垃圾(确切地说是4606858)初始化而不是0u.这让我感到惊讶,所以我去了cppreference.com,看看上面那条线应该做什么......


根据我的内容,表单T object{};对应于值初始化.我觉得这句话很有意思:

在所有情况下,如果使用空的大括号{}并且T是聚合类型,则执行聚合初始化而不是值初始化.

但是,gray_code有一个用户提供的构造函数.因此,它不是聚合,因此不执行聚合初始化.gray_code没有构造函数采用std::initializer_list如此列表初始化也不执行.然后,值初始化gray_code应遵循通常的C++ 14值初始化规则:

1)如果T是没有默认构造函数的类类型,或者是用户提供的默认构造函数或者删除了默认构造函数,则该对象是默认初始化的.

2)如果T是没有用户提供或删除的默认构造函数的类类型(也就是说,它可能是具有默认默认构造函数的类或具有隐式定义的类),则该对象被零初始化然后它是default-initialized如果它有一个非平凡的默认构造函数.

3)如果T是数组类型,则数组的每个元素都是值初始化的.

4)否则,对象被零初始化.

如果我读得正确,gray_code有一个明确的默认(非用户提供)默认构造函数,因此1)不适用.它有一个默认的默认构造函数,因此2)apply:gray_code零初始化的.默认的默认构造函数似乎满足了普通默认构造函数的所有要求,因此不应该进行默认初始化.让我们看一下如何gray_code进行零初始化:

  • 如果T是标量类型,则对象的初始值是隐式转换为T的整数常量零.

  • 如果T是非联合类类型,则所有基类和非静态数据成员都是零初始化的,并且所有填充都初始化为零位.构造函数(如果有)将被忽略.

  • 如果T是并集类型,则第一个非静态命名数据成员将进行零初始化,并将所有填充初始化为零位.

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

  • 如果T是引用类型,则不执行任何操作.

gray_code是一个非联合类类型.因此,应初始化其所有非静态数据成员,这意味着value零初始化.value满足std::is_unsigned并因此是标量类型,这意味着它应该用"隐式转换为T的积分常数零"初始化.

所以,如果我正确地阅读了所有这些,在foo上面的函数中,bar.value应该始终用0它初始化,它永远不应该用垃圾初始化,我是对的吗?

注意:我编译我的代码的编译器是MinGW_w4 GCC 4.9.1(POSIX线程和dwarf异常)以防万一.虽然我有时会在我的计算机上弄脏垃圾,但我从来没有通过在线编译器获得除零以外的任何东西.


更新:似乎是一个GCC错误,错误是我的,而不是我的编译器.实际上,在写这个问题时,我为了简单起见而假设

class foo {
    foo() = default;
};
Run Code Online (Sandbox Code Playgroud)

class foo {
    foo();
};

foo::foo() = default;
Run Code Online (Sandbox Code Playgroud)

是等价的.他们不是.以下是C++ 14标准的引用,[dcl.fct.def.default]部分:

如果函数是用户声明的,并且在第一个声明中未明确默认或删除,则用户提供该函数.

换句话说,当我得到垃圾值时,我的默认默认构造函数确实是用户提供的,因为它在第一次声明时没有明确表示.因此,发生的事情不是零初始化而是默认初始化.再次感谢@Columbo指出真正的问题.

Col*_*mbo 10

所以,如果我正确地阅读了所有这些,在foo上面的函数中, bar.value应该始终用0它初始化,它永远不应该用垃圾初始化,我是对的吗?

是.您的对象是直接列表初始化的.C++ 14的*[dcl.init.list]/3指定了

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

  • [......不适用的要点...]

  • 否则,如果T是聚合,则执行聚合初始化(8.5.1).

  • 否则,如果初始化程序列表没有元素且T是具有默认构造函数的类类型,则对象将进行值初始化.

  • [...]

您的类不是聚合,因为它具有用户提供的构造函数,但它具有默认构造函数.[dcl.init]/7:

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

  • if T是一个(可能是cv限定的)类类型(第9条),没有默认构造函数(12.1)或者是用户提供或删除的默认构造函数,那么该对象是默认初始化的;

  • 如果T是一个(可能是cv限定的)类类型而没有用户提供或删除的默认构造函数,那么该对象是零初始化的,并且检查默认初始化的语义约束,如果T有一个非平凡的默认构造函数,该对象是默认初始化的;

[dcl.fct.def.default]/4:

如果特殊成员函数是用户声明的,并且在其第一个声明中未明确默认,则由用户提供.

因此,您的构造函数不是用户提供的,因此该对象是零初始化的.(构造函数因其无关紧要而未被调用)

最后,如果不清楚,零初始化对象或类型的引用T意味着:

  • 如果T是标量类型(3.9),则将对象初始化为通过将整数文字0(零)转换为T;

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

  • [...]


因此

  • 你的编译器被窃听了

  • ...或者您的代码在某些其他方面触发未定义的行为.


*答案在C++ 11中仍然是肯定的,尽管引用的部分并不相同.