花括号初始化列表中对象的创建顺序

15
#include <iostream>

struct A
{
    A() { std::cout << "(A::A)"; }
};

struct B
{
    B() { std::cout << "(B::B)"; }
};

struct C
{
    template<typename ...Args>
    C(Args && ...) {}
};

int main(int agrc, char *argv[])
{
    C {A(), B()}; // <-- prints (B::B)(A::A)
    std::cout << std::endl;
    C {(A(), B())}; // <-- prints (A::A)(B::B)
    std::cout << std::endl;

    return 0;
}

我有两个问题:

  • 为什么在第一个花括号初始化列表中对象是从右往左创建的?
  • 为什么在第二种情况下,用括号会反转这个顺序?

编辑:我使用了msvs 2013进行编译。


11
清晰地再现问题。 - Puppy
2
@dyp。我不这么认为。在列表初始化中,给定初始化器子句的每个值计算和副作用都在逗号分隔的初始化器列表中跟随它的任何初始化器子句相关的每个值计算和副作用之前排序。 - sliser
@DeadMG - 简单的解决方案就是一开始不要写烂代码。 - Ed Heal
此外,有时编写一些愚蠢的代码也是很有趣的... - Joseph Mansfield
@Nawaz:我说那不是我的名字 :P - Lightness Races in Orbit
显示剩余13条评论
3个回答

21
在第二个示例中,实际上您只使用B()进行初始化;通过使用逗号运算符,首先构造并且丢弃了A()
C {(A(), B())};
//|^^^^^^^^^^|
//      \--------> (A(), B())
//                  ^^^  ^^^
//                   |    |
//                   /    \
//            evaluated,   THEN evaluated,
//            discarded      used

另一方面,在第一个实例中,您通过初始化列表从两个临时变量初始化了C,这些元素也应该按照从左到右的顺序进行评估,正如它发生的那样,但是您的编译器在这方面有缺陷:

[C++11:8.5.4/4]:大括号初始化列表中,包括由展开包(14.5.3)产生的任何初始化子句都按它们出现的顺序进行评估。也就是说,与给定初始化子句相关联的每个值计算和副作用在逗号分隔的初始化列表中跟随它的任何初始化子句相关联的每个值计算和副作用之前编排。 [注意:即使初始化的语义不同,此评估顺序仍然适用;例如,当将初始化列表的元素解释为构造函数调用的参数时,它适用,即使通常对于调用的参数没有排序约束。 —end note]

我可以使用GCC 4.8*重现问题,但Clang 3.5行为正确。这个bug之前已经在std-discussion列表上讨论过,但我还没有找到GCC Bugzilla ID§

C {A(), B()};
// ^^^  ^^^
//  |    \
// eval-  THEN
// uated   evaluated
//  \       /
//   \     /
//  both used

* http://coliru.stacked-crooked.com/a/1f18e0d1f8973f3c
http://coliru.stacked-crooked.com/a/5a6e7506e9be97c3
https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/TQUnBFkUBDg
§ #51253可能与此有关。


@GrijeshChauhan:你喜欢它吗? - Lightness Races in Orbit
是的,我喜欢ASCII艺术,我也使用一个工具来绘制:)。顺便恭喜你获得了10万+的关注,上次我看到你的时候还在98k左右。 - Grijesh Chauhan
1
@GrijeshChauhan:时间飞逝! - Lightness Races in Orbit
1
#51253 就是这个 bug。请参考来自该 bug 测试用例的 gccclang 的实时示例。 - iavr

5

为什么在第一个花括号初始化列表中,对象是从右到左创建的?

不对。应该是从左到右。如果您的编译器正在从右到左评估,则表示其存在错误。已知GCC(4.8)存在此错误。您使用的是GCC吗?

为什么第二种情况中的括号会颠倒这个顺序?

同样是从左到右。在这种情况下,逗号运算符进入图片,按照从左到右的顺序计算操作数。


使用 MSVS 2013 编译 - sliser
1
@sliser:如果你所说的是真的,那么那个也有漏洞。 - Nawaz

2

这是gcc 4.8.1的一个旧bug(我猜你使用GCC)或其他编译器的问题。几个月前,我写过关于这个bug的文章 the initializer-list: a bug of GCC 4.8.1

虽然它是用俄语写的,但是你可以使用谷歌服务翻译将其翻译成英语。

正如其他人所说,initializer-list的元素从左到右进行评估,并且在评估下一个元素之前应用所有副作用。

在您的第二个代码示例中,实际上调用了一个表达式为逗号运算符的构造函数。逗号运算符实际上的行为方式与initializer-list相同,即它从左到右评估其操作数,并在评估下一个操作数之前应用副作用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接