繁体   English   中英

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

[英]Value initialization: default initialization or zero initialization?

我有模板化的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...
};

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

template<typename UnsignedInt>
void foo( /* ... */ )
{
    gray_code<UnsignedInt> bar{};
    // Other stuff...
}

在这段代码中,我期望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)适用: gray_code零初始化的 默认的默认构造函数似乎满足了普通默认构造函数的所有要求,因此不应该进行默认初始化。 让我们看看gray_code是如何进行零初始化的:

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

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

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

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

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

gray_code是一种非联合类类型。 因此,应初始化其所有非静态数据成员,这意味着该value为零初始化。 value满足std::is_unsigned ,因此是标量类型,这意味着它应该用“隐式转换为T的整数常量零”进行初始化。

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

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


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

class foo {
    foo() = default;
};

class foo {
    foo();
};

foo::foo() = default;

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

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

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

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

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

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

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

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

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

  • [...]

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

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

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

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

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

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

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

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

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

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

  • [...]


因此

  • 你的编译器被窃听了

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


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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM