[英]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的整数常量零”进行初始化。
所以,如果我正确地读了所有这些,在上面的函数foo
, bar.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指出真正的问题。
所以,如果我正确地读了所有这些,在上面的函数
foo
,bar.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.