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中仍然是肯定的,尽管引用的部分并不相同.
| 归档时间: |
|
| 查看次数: |
1255 次 |
| 最近记录: |