17

在回答了一些问题后,我今天构建了这个实验

struct A { 
  bool &b; 
  A(bool &b):b(b) { } 
  ~A() { std::cout << b; }  
  bool yield() { return true; } 
}; 

bool b = A(b).yield();

int main() { }

b在通过动态初始化false将其设置为之前具有值(由零初始化产生)。true如果在初始化完成之前临时被销毁b,我们将打印false,否则true

规范说临时在完整表达式结束时被销毁。这似乎与b. 所以我想知道

  • 规范是否允许实现在不同的运行中false同时打印?true

上面的Clang 打印false,而 GCC 打印true. 这让我很困惑。我是否错过了一些定义订单的规范文本?

4

3 回答 3

8

我认为它可以打印出真假,或者出于某种不相关的原因,什么都没有。

真假部分是(如您所说),临时A对象的销毁相对于b.

根本不可能是因为 的初始化b没有相对于std::cout;的创建/初始化进行排序 当您尝试销毁临时文件时,cout可能尚未创建/初始化,因此尝试打印某些内容可能根本不起作用。[编辑:这是特定于 C++98/03,不适用于 C++11。]

编辑:这是我至少看到顺序的方式:

在此处输入图像描述

Edit2:在重读 §12.2/4(又一次)之后,我再次更改了图表。§12.2/4 说:

在两种情况下,临时对象在与完整表达式结尾不同的点被销毁。第一个上下文是当表达式作为定义对象的声明符的初始值设定项出现时。在这种情况下,保存表达式结果的临时变量将持续存在,直到对象的初始化完成。该对象是从临时副本初始化的;在这个复制过程中,一个实现可以多次调用复制构造函数;临时对象在被复制后、初始化完成之前或初始化完成时被销毁。

我相信这个表达式是定义对象的声明器的初始化器,因此需要从表达式的值的副本(true在这种情况下)初始化对象,而不是直接从返回值。在 的情况下true,这可能是没有区别的区别,但我认为该图在技术上更准确,因为它现在代表。

这也很清楚(我认为)临时持有true不必完整表达式结束时被销毁,所以我重新绘制了图表以反映这一点。

这部分在 C++0x/C++11 中消失了,所以我重新绘制了图表(再次)以显示两者之间的区别(以及这部分在 C++11 中变得多么简单) .

于 2011-04-22T23:40:39.347 回答
2

首先,只是为了清除之前的段落,在这里使用b它自己的(动态)初始化不是 UB。在计算表达式之前,b不是未初始化而是零初始化。


临时的A必须与完整的表达式一样长:

临时对象被销毁作为评估完整表达式(1.9)的最后一步,该完整表达式(在词法上)包含它们被创建的点。

[ISO/IEC 14882:2003(E) 12.2/3]

该行bool b = A(b).yield();是一个声明,它是一个语句,而不是一个表达式。手头的表达式只能在 . 的 RHS 中找到=。[ISO/IEC 14882:2003(E) A.6]

这意味着应该在动态初始化发生之前销毁临时文件,不是吗?当然,该值true保存在包含表达式1的结果的临时文件中,直到初始化完成,但原始A临时文件应在b实际修改之前被销毁。

因此,我false每次都期望输出。


1

第一个上下文是当表达式作为定义对象的声明符的初始值设定项出现时。在这种情况下,保存表达式结果的临时变量将持续存在,直到对象的初始化完成”

[ISO/IEC 14882:2003(E) 12.2/4]

于 2011-04-22T23:55:08.253 回答
2

(引用 C++03 标准)

首先是§12.2/3:

当实现引入具有非平凡构造函数(12.1)的类的临时对象时,它应确保为临时对象调用构造函数。类似地,应该使用非平凡的析构函数(12.4)调用析构函数。临时对象被销毁作为评估完整表达式(1.9)的最后一步,该完整表达式(在词法上)包含它们被创建的点。即使评估以抛出异常结束也是如此。

由于§1.9/13,我相信这是一条红鲱鱼:

[注意:C++ 中的某些上下文会导致对由除表达式 (5.18) 之外的句法结构产生的完整表达式求值。例如,在 8.5 中,初始化器的一种语法是

    ( expression-list )

但生成的构造是对带有表达式列表作为参数列表的构造函数的函数调用;这样的函数调用是一个完整的表达式。例如,在 8.5 中,初始化器的另一种语法是

    = initializer-clause

但同样,生成的构造可能是对构造函数的函数调用,其中一个赋值表达式作为参数;同样,函数调用是一个完整的表达式。]

对我来说,这意味着它A(b).yield()本身就是一个完整的表达方式,使得 §12.2/3 在这里无关紧要。

然后我们进入序列点——§1.9/7:

访问由 volatile 左值 (3.10) 指定的对象、修改对象、调用库 I/O 函数或调用执行任何这些操作的函数都是副作用,它们是执行环境状态的变化。表达式的评估可能会产生副作用。在被称为序列点的执行序列中的某些指定点处,先前评估的所有副作用都应是完整的,并且后续评估的副作用不应发生。

§1.9/16:

每个完整表达式的评估完成时都有一个序列点。

和§1.9/17:

调用函数时(无论函数是否内联),在对所有函数参数(如果有)求值之后都会有一个序列点,该序列点发生在函数体中的任何表达式或语句执行之前。在复制返回值之后和执行函数外的任何表达式之前还有一个序列点。

综上所述,我认为Clang 是正确的,而 GCC(和 MSVC 2010 SP1)是错误的——保存表达式结果的临时变量(根据 §12.2/4 延长其生命周期)是从bool返回的A::yield(),而不是调用的临时A对象。yield考虑到第 1.9 节,调用后应该有一个序列点,在此A::yield()期间临时A对象被销毁。

于 2011-04-22T23:49:22.423 回答