Wak*_*zil 10 c++ initialization language-lawyer c++11
这个代码在编译Coliru与警告[未初始化成员a[1].i和a[2].i在std::cout <<在表达式main()]但在通常编译Ideone.
#include <iostream>
struct A
{
int i;
A(int j) : i{j} {};
A() = default;
};
int main() {
A a[3] = { A(1) };
std::cout << a[1].i << ' ' << a[2].i << '\n';
}
Run Code Online (Sandbox Code Playgroud)
根据我对iso§8.5p7的解释,Ideone是正确的,因为本节中的第4个要点.
这是N3797的§8.5p7
对值类型T的对象进行值初始化意味着:
- 如果T是一个(可能是cv-quali fi ed)类类型(第9条),没有默认构造函数(12.1)或者是用户提供或删除的默认构造函数,那么该对象是默认初始化的;
- 如果T是一个(可能是cv-quali fi ed)类类型而没有用户提供或删除的默认构造函数,那么该对象是零初始化的,并且检查默认初始化的语义约束,如果T有一个非平凡的默认构造函数,该对象是默认初始化的;
- 如果T是数组类型,那么每个元素都是值初始化的;
- 否则,该对象被零初始化.
值初始化的对象被视为构造,因此受本国际标准的规定适用于"构造"对象,"构造函数已完成的对象"等,即使没有为该对象调用构造函数也是如此.初始化.
就C++ 14(N3797)而言,Ideone是正确的(参见Casey对C++ 11的回答),因为a[1]并a[2]初始化了A{},这是值初始化,导致i为0.这来自N3797§8.5.1/ 7:
如果列表中的初始化子句比集合中的成员少,则未明确初始化的每个成员应从其大括号或等号初始值初始化,或者,如果没有大括号或等于初始值,则来自一个空的初始化列表(8.5.4).[例如:
struct S { int a; const char* b; int c; int d = b[a]; };
S ss = { 1, "asdf" };
Run Code Online (Sandbox Code Playgroud)
使用带有"asdf"的ss.b初始化ss.a,使用int {}形式的表达式(即0)初始化ss.c,使用ss.b [ss]的值初始化ss.d .a](即's')
数组是符合§8.5.1/ 1(An aggregate is an array...)的聚合,因此这适用于数组的初始化.
表达式T{}(一个空的初始化列表)值初始化每个§8.5.4/ 3的对象:
否则,如果初始化程序列表没有元素且T是具有默认构造函数的类类型,则对象将进行值初始化.
我们可以确认值初始化i的值为0,其中§8.5/ 8:
如果T是一个(可能是cv-quali fi ed)类类型而没有用户提供或删除的默认构造函数,那么该对象是零初始化的,并且检查默认初始化的语义约束,如果T有一个非平凡的默认构造函数,该对象是默认初始化的;
根据§8.4.2/ 4,您的类没有用户提供的默认构造函数:
如果函数是用户声明的,并且未在其第一个声明中明确默认或删除,则用户提供该函数.
一个有趣的注意事项是,如果您的默认构造函数是用户提供的并且未初始化i,则将使用8.5/8的第一个点,并且i将保持未初始化,如此示例中所示.
最后,关于比较的小记.Ideone使用了几种不同版本的GCC.使用哪一个有所不同(__VERSION__如果需要,可以查看).在这种情况下,编译器标志也略有不同.如果-std=c++1y存在(并且除了使用添加的功能之外我不知道检查其他方法),将会有一些C++ 14支持,但不是完全支持,因此小的更改(T{}相对于T(),从大括号初始化)可能无法实现-or-equal-initializer和检查默认初始化的语义约束.事实上,你甚至可以检查第一个.Coliru允许您配置构建命令,因此只是说Coliru非常模糊.
无论哪种方式,使用N3797测试符合标准的行为都不值得,直到有足够的C++ 14支持(或者至少在它标准化之前).在这种情况发生之前,我倾向于坚持使用N3485.在这个具体的例子中,我认为两种标准的行为没有任何区别.检查凯西关于这两个标准在这个问题上有何不同的答案.你有一个转换构造函数,所以你的对象将在C++ 11中默认初始化.
对于具有默认默认构造函数和另一个非默认构造函数的类,C++ 11和N3797之间的值初始化行为存在明显差异.C++11§8.5/ 7:
对值类型T的对象进行值初始化意味着:
- 如果T是具有用户提供的构造函数(12.1)的(可能是cv限定的)类类型(第9节),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则初始化是错误的) ;
- 如果T是一个(可能是cv限定的)非联合类类型而没有用户提供的构造函数,那么该对象是零初始化的,如果T的隐式声明的默认构造函数是非平凡的,则调用该构造函数.
- 如果T是数组类型,那么每个元素都是值初始化的;
- 否则,该对象被零初始化.
N3797§8.5/ 8:
对值类型T的对象进行值初始化意味着:
- 如果T是一个(可能是cv限定的)类类型(第9条),没有默认构造函数(12.1)或者是用户提供或删除的默认构造函数,那么该对象是默认初始化的;
- 如果T是一个(可能是cv限定的)类类型而没有用户提供或删除的默认构造函数,那么该对象是零初始化的,并且检查默认初始化的语义约束,如果T有一个非平凡的默认构造函数,该对象是默认初始化的;
- 如果T是数组类型,那么每个元素都是值初始化的;
- 否则,该对象被零初始化.
您struct A有一个用户声明的默认构造函数A() = default;和一个用户提供的非默认构造函数A(int j) : i{j} {}.在C++ 11中,它受第一个项目符号的影响:它有一个用户提供的构造函数,因此默认构造函数被调用(它什么都不做:A默认构造函数很简单).在N3797中,第二个项目符号适用,因为A"没有用户提供或删除的默认构造函数",因此对象是零初始化的.
简而言之,使用任何用户提供的构造函数在类的对象的C++ 11中进行值初始化将不会在默认初始化之前执行零初始化.在N3797中,没有用户提供的默认构造函数的类对象的值初始化将在默认初始化之前执行零初始化.
似乎Coliru上的clang版本一直在跟踪C++ 11后的标准,但是GCC 4.8没有.
编辑:该测试程序证明了GCC 4.8实际上不遵循值初始化N3797规则.问题似乎是默认情况下 -初始化没有提供初始值设定项而不是值的数组元素 - 根据标准的要求初始化它们.注意第二个数组元素(显式提供空初始化器)和第三个没有提供初始化器的第三个元素之间的行为差异.
这看起来像是一个可能的GCC错误.
编辑:在Ideone上由相同的GCC版本编译的相同测试程序没有演示该错误.不知道这里发生了什么.也许影响Ideone输出的不同编译器标志,我不知道如何确定使用的编译器命令行.
| 归档时间: |
|
| 查看次数: |
724 次 |
| 最近记录: |