【问题标题】:The snippet compiles with warnings in Coliru, but compiles normally in Ideone. Which one is correct?该片段在 Coliru 中编译时带有警告,但在 Ideone 中编译正常。哪一个是正确的?
【发布时间】:2014-01-05 17:15:41
【问题描述】:

此代码在 Coliru 中编译并带有警告 [main() 中的 std::cout << 表达式中的单元化成员 a[1].ia[2].i] 但在 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.5 p7 的解释,Ideone 是正确的,因为本条款的第 4 个要点。

这是来自 N3797 的 §8.5 p7

对 T 类型的对象进行值初始化意味着:

  • 如果 T 是(可能是 cv 限定的)类类型(第 9 条),没有默认构造函数 (12.1) 或默认构造函数是 用户提供或删除,则对象默认初始化;
  • 如果 T 是一个(可能是 cv 限定的)类类型,没有用户提供或删除的默认构造函数,则该对象为零初始化 并检查默认初始化的语义约束, 如果 T 有一个重要的默认构造函数,则该对象是 默认初始化;
  • 如果T是数组类型,那么每个元素都是值初始化的;
  • 否则,对象被零初始化。

值初始化的对象被视为已构造并且 因此受本国际标准的规定适用于 “构造的”对象,构造函数拥有的“对象” 完成,”等等,即使没有为对象的构造函数调用 初始化。

【问题讨论】:

  • Coliru 是正确的(尽管您没有指定使用的编译器)。
  • 等等,我可能错了。
  • 警告因编译器而异,没有对错。
  • @chris 我的钱(不多,授予)将在 ideone 上是正确的。我希望对数组中的其他As 进行值初始化,以初始化相应的is。
  • @juanchopanza,是的,这就是 int a[3] = {1};a[1]a[2] 保留为 0 的全部内容,但改为使用课程,并且出于某种原因,这是您学到的一条规则在我一开始总是忘记的初学者课程中。

标签: c++ c++11 initialization language-lawyer


【解决方案1】:

就 C++14 (N3797) 而言,Ideone 是正确的(请参阅 Casey 对 C++11 的回答),因为 a[1]a[2] 是用 A{} 初始化的,这是值初始化,导致 @ 987654326@ 为 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.a,用“asdf”初始化 ss.b,用 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 限定的)类类型,没有用户提供或删除的默认构造函数,则该对象被零初始化并检查默认初始化的语义约束,如果 T 有一个非平凡的默认构造函数,对象是默认初始化的;

根据第 8.4.2/4 节,您的类没有用户提供的默认构造函数:

一个函数是用户提供的,如果它是用户声明的并且没有明确地默认或者 在第一次声明时删除。

需要注意的一个有趣的点是,如果您的默认构造函数是用户提供的并且没有初始化 i,则将使用 8.5/8 的第一个点,而 i 将保持未初始化,如您在这个example

最后,关于比较的一个小说明。 Ideone 使用了几个不同版本的 GCC。使用哪一个会有所不同(如果需要,您可以与__VERSION__ 联系)。在这种情况下,编译器标志也略有不同。如果存在-std=c++1y(我不知道除了使用附加功能之外的其他检查方法),将会有一些 C++14 支持,但不完全支持,所以小的变化(T{} vs .T(),从大括号或相等初始化器初始化并检查默认初始化的语义约束)可能无法实现。事实上,你甚至可以check the first。 Coliru 允许您配置构建命令,因此仅说 Coliru 是非常模棱两可的。

无论哪种方式,在足够的 C++14 支持出现之前(或至少在它被标准化之前),使用 N3797 来测试一致性行为都不太值得。在这种情况发生之前,我倾向于坚持使用 N3485。在这个具体的例子中,我认为这两个标准的行为没有任何区别。请查看 Casey 的回答,了解这两个标准在这个问题上有何不同。您有一个转换构造函数,因此您的对象将在 C++11 中默认初始化。

【讨论】:

  • 你知道“检查默认初始化的语义约束”是什么意思吗?
  • @WakeupBrazil,这不是我的想法,但仅在更新对 N3797 的引用时出现,所以它不在 C++11 中。
  • 我也注意到了。
  • 我同意你所说的一切,除了一个例外:A 的构造函数是微不足道的,那么你必须到第 4 个要点才能得出结论 a.i = 0
  • @WakeupBrazil,好点子。虽然您的示例没有达到这一点,但我的定义实际上是微不足道的,这应该会导致零初始化。如果现在已经在 Clang 中解决了这个问题,我也不会感到惊讶,也不会对此感到惊讶。我将示例更新为非平凡的,并对其措辞进行了一些更新。
【解决方案2】:

对于同时具有默认默认构造函数和另一个非默认构造函数的类,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 中,second 项目符号适用,因为 A 是“没有用户提供或删除的默认构造函数”,因此该对象是零初始化的。

简单地说,具有any用户提供的构造函数的类的对象在C++11中的值初始化不会在默认初始化之前执行零初始化。在 N3797 中,没有用户提供的 default 构造函数的类的对象的值初始化将在默认初始化之前执行零初始化。

似乎the version of clang on Coliru 一直在跟踪 C++11 之后的标准,但 GCC 4.8 has not

编辑:This test program 表明 GCC 4.8 实际上确实遵循 N3797 值初始化规则。问题似乎在于它是default——初始化没有提供初始化程序的数组元素,而不是value——按照标准的要求初始化它们。注意第二个数组元素之间的行为差​​异,它显式提供了一个空初始化器,第三个没有提供初始化器。

这看起来像是一个可能的 GCC 错误。

编辑:The same test program compiled by the same GCC version on Ideone 没有展示该错误。不知道这里发生了什么。也许不同的编译器标志会影响 Ideone 上的输出,我不知道如何确定使用的编译器命令行。

【讨论】:

  • 好收获。我已经读了好几遍了,但我从来没有真正意识到它有那么大的不同。
  • 优秀的答案。但是我仍然怀疑不必去 N3797 中的第 4 个要点,因为 A 的默认构造函数是微不足道的。你不同意吗?
  • @WakeupBrazil 正如我在答案中所说,N3797 的第二个项目符号适用:A 是“没有用户提供或删除的默认构造函数的类类型”。由于第二个项目符号适用,因此第四个项目符号不适用,因为它以“否则”开头。
  • 好的,我接受。但是,你能给我举个例子来考虑第 4 个要点吗?
  • @WakeupBrazil 当然:Tunsigned char。我认为标准的意图是所有类类型都匹配第一个或第二个项目符号,所有非类类型匹配第三个或第四个。
【解决方案3】:

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 也不会发出警告。

明确地说:它们实际上是相同的编译器,并且具有相同的标志,它们的行为完全相同,因此它们都是正确的。

【讨论】:

  • -Wall 不会启用所有警告,即使名称看起来确实如此。
  • 然而,这并不能真正回答“哪个是正确的”这个问题。
  • @juanchopanza,因为事实上,他们都使用相同的标志做同样的事情?所以答案是含蓄的:它们都是正确的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多