此代码在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';
}
根据我对iso§8.5p7的解释,Ideone是正确的,因为本节中的第4个要点。
这是来自N3797的§8.5p7
对T类型的对象进行值初始化意味着:
- 如果T是一个(可能是cv-quali fi ed)类类型(第9条),没有默认构造函数(12.1)或默认构造函数 用户提供或删除,然后该对象被默认初始化;
- 如果T是(可能是cv-quali fi ed)类类型而没有用户提供或删除的默认构造函数,则该对象为零初始化 并检查默认初始化的语义约束, 如果T有一个非平凡的默认构造函数,那么对象就是 缺省初始化;
- 如果T是数组类型,则每个元素都是值初始化的;
- 否则,该对象为零初始化。
值初始化的对象被视为构造和 因此,本国际标准的规定适用于 “构造”对象,“构造函数具有的对象” 完成,“等等,即使没有为对象调用构造函数 初始化。
答案 0 :(得分:9)
就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" };
使用1,ss.b使用“asdf”初始化ss.a,使用int {}形式的表达式(即0)初始化ss.c,使用ss的值初始化ss.d。 b [ss.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
将保持未初始化状态,如你可以在这个example中看到。
最后,关于比较的小记录。 Ideone使用了几种不同版本的GCC。使用哪一个会有所不同(如果需要,可以查看__VERSION__
)。在这种情况下,编译器标志也略有不同。如果-std=c++1y
存在(并且我不知道除了使用添加的功能之外的其他方法),那么将会有一些C ++ 14支持,但不是完全支持,因此小的更改({{可能无法实现1}}与T{}
,从大括号或等于初始化器初始化并检查默认初始化的语义约束。事实上,你甚至可以check the first。 Coliru允许您配置构建命令,因此只是说Coliru非常模糊。
无论哪种方式,使用N3797测试符合标准的行为都不值得,直到有足够的C ++ 14支持(或者至少在它标准化之前)。在这种情况发生之前,我倾向于坚持使用N3485。在这个具体的例子中,我认为这两个标准的行为没有任何区别。检查Casey对这两个标准在这个问题上有何不同的答案。你有一个转换构造函数,所以你的对象将在C ++ 11中默认初始化。
答案 1 :(得分:8)
对于具有默认默认构造函数和另一个非默认构造函数的类,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中,没有用户提供的默认构造函数的类对象的值初始化将在默认初始化之前执行零初始化。
似乎the version of clang on Coliru在C ++ 11后跟踪标准,但是{{3}}。
编辑:GCC 4.8 has not演示GCC 4.8实际 遵循N3797规则进行值初始化。问题似乎是默认 - 初始化没有提供初始值设定项的数组元素而不是 value - 根据标准的要求初始化它们。请注意第二个数组元素(显式提供空初始化程序)和第三个没有初始化程序的数据元素之间的行为差异。
这看起来像是一个可能的GCC错误。
编辑:This test program没有证明该错误。不知道这里发生了什么。也许影响Ideone输出的不同编译器标志,我不知道如何确定使用的编译器命令行。
答案 2 :(得分:4)
Coliru的默认编译命令是:
g++-4.8 -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
它使用-Wall
,可以发出更广泛的警告。为了迂腐,它可以发出以下警告:
-Waddress
-Warray-bounds (only with -O2)
-Wc++11-compat
-Wchar-subscripts
-Wenum-compare (in C/ObjC; this is on by default in C++)
-Wimplicit-int (C and Objective-C only)
-Wimplicit-function-declaration (C and Objective-C only)
-Wcomment
-Wformat
-Wmain (only for C/ObjC and unless -ffreestanding)
-Wmaybe-uninitialized
-Wmissing-braces (only for C/ObjC)
-Wnonnull
-Wopenmp-simd
-Wparentheses
-Wpointer-sign
-Wreorder
-Wreturn-type
-Wsequence-point
-Wsign-compare (only in C++)
-Wstrict-aliasing
-Wstrict-overflow=1
-Wswitch
-Wtrigraphs
-Wuninitialized
-Wunknown-pragmas
-Wunused-function
-Wunused-label
-Wunused-value
-Wunused-variable
-Wvolatile-register-var
有关这些的更多信息可以找到here。
尽管Ideone也使用GCC 4.8,但很可能没有设置-Wall
。如you can see,没有那个标志,Coliru也没有警告。
要明确:它们实际上是相同的编译器,并且具有相同的标志,它们的行为完全相同,因此它们都是正确的。