我有模板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{};
形式对应value initialization。我觉得这句话很有意思:
在所有情况下,如果使用空的大括号{}并且T是聚合类型,则执行聚合初始化而不是值初始化。
但是,gray_code
具有用户提供的构造函数。因此,它不是聚合,因此不执行aggregate initialization。 gray_code
没有构造函数使用std::initializer_list
,因此list initialization也未执行。然后,gray_code
的值初始化应遵循通常的C ++ 14值初始化规则:
1)如果T是没有默认构造函数或用户提供的默认构造函数或删除的默认构造函数的类类型,则对象是默认初始化的。
2)如果T是没有用户提供或删除的默认构造函数的类类型(也就是说,它可能是具有默认默认构造函数或隐式定义的类的类),则该对象为零初始化并且如果它有一个非平凡的默认构造函数,则默认初始化。
3)如果T是数组类型,则数组的每个元素都是值初始化的。
4)否则,对象被零初始化。
如果我读得正确,gray_code
有一个明确的默认(非用户提供的)默认构造函数,因此1)不适用。它有一个默认的默认构造函数,因此2)适用:gray_code
是zero-initialized。默认的默认构造函数似乎满足了普通默认构造函数的所有要求,因此不应该进行默认初始化。让我们看一下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指出真正的问题。
答案 0 :(得分:10)
所以,如果我在上面的函数
foo
中正确阅读了所有这些内容, 应始终使用bar.value
初始化0
,而且永远不应该{。}} 用垃圾初始化,我是对的吗?
是。您的对象是直接列表初始化的。 C ++ 14's * [dcl.init.list] / 3指定
定义了
T
类型的对象或引用的列表初始化 如下:
[......不适用的要点......]
否则,如果
T
是聚合,则执行聚合初始化(8.5.1)。否则,如果初始化列表没有元素且T是具有默认构造函数的类类型,则该对象是值初始化的。
[...]
您的类不是聚合,因为它具有用户提供的构造函数,但它具有默认构造函数。 [dcl.init] / 7:
value-initialize
T
类型的对象意味着:
如果
T
是一个(可能是cv限定的)类类型(第9条),没有默认构造函数(12.1)或默认构造函数 用户提供或删除,然后对象默认初始化;如果T是(可能是cv限定的)类类型没有用户提供或删除的默认构造函数,那么对象是 零初始化和语义约束 检查默认初始化,如果
T
具有非平凡性 默认构造函数,该对象是默认初始化的;
[dcl.fct.def.default] / 4:
特殊成员函数是用户提供的,如果它是用户声明的和 没有在第一次声明中明确违约。
因此,您的构造函数不是用户提供的,因此该对象是零初始化的。 (构造函数因其无关紧要而未被调用)
最后,如果不清楚,零初始化类型T
的对象或引用意味着:
如果
T
是标量类型(3.9),则将对象初始化为通过将整数文字0
(零)转换为T
获得的值;如果
T
是一个(可能是cv限定的)非联合类类型,则每个非静态数据成员和每个基类子对象都是零初始化的,并且填充初始化为零位;[...]
因此
您的编译器被窃听
...或者您的代码在某个其他位置触发未定义的行为。
*在C ++ 11中答案仍然是肯定的,尽管引用的部分并不相同。