什么时候初始化器暂时销毁?

时间:2011-04-22 23:06:43

标签: c++ initialization language-lawyer temporary-objects order-of-execution

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

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的初始化无关。所以我想知道

  • 规范是否允许实施在不同的运行中同时打印falsetrue

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

3 个答案:

答案 0 :(得分:8)

我认为允许打印出真实或错误,或者出于某些无关的原因,一无所获。

真或假的部分是(正如你所说的),临时A对象的破坏与b的动态初始化无关。

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

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

enter image description here

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

  

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

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

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

这部分已经在C ++ 0x / C ++ 11中消失了,所以我重新绘制了图表(再次)以显示两者之间的差异(以及这篇文章在C +中有多简单) 11)。

答案 1 :(得分:2)

(引用C ++ 03标准)

首先是§12.2/ 3:

  

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

我认为这是一个红鲱鱼,因为§1.9/ 13:

  

[注意:C ++中的某些上下文会导致评估由表达式(5.18)以外的语法结构产生的完整表达式。例如,在8.5中,初始化程序的一种语法是

     

( expression-list )

     

但是结果构造是对构造函数的函数调用,其中expression-list作为参数列表;这样的函数调用是一个完整的表达式。例如,在8.5中,初始化程序的另一种语法是

     

= initializer-clause

     

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

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

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

  

访问由volatile lvalue(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被销毁。

答案 2 :(得分: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临时值应在{{1}之前销毁实际上是修改过的。

因此,我希望每次输出b


1

  

第一个背景是什么时候   表达式显示为初始化程序   用于定义对象的声明符。   在那种情况下,暂时的   保存表达式的结果   应该坚持到对象为止   初始化完成“

     

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