初始化器到底什么时候被临时销毁?

发布于 2024-11-03 05:55:22 字数 626 浏览 5 评论 0原文

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

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。这让我很困惑。我是否错过了一些定义订单的规范文本?

I constructed this experiment today, after answering some question

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 has value false (resulting from zero initialization) before setting it to true by the dynamic initialization. If the temporary is destroyed before initialization of b finished, we will print false, otherwise true.

The spec says that the temporary is destroyed at the end of the full-expression. That does not seem to be ordered with the initialization of b. So I wonder

  • Does the spec allow an implementation to print both false and true in different runs?

Clang prints false for the above, while GCC prints true. This confuses me. Did I miss some spec text defining the order?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

债姬 2024-11-10 05:55:22

我认为它可以打印出 true 或 false,或者出于某种不相关的原因,什么也不打印。

正确或错误的部分是(正如您所说的),临时 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 中变得简单了多少) 。

I think it's allowed to print out true, or false, or for somewhat unrelated reasons, nothing at all.

The true or false part is (as you've said), that the destruction of the temporary A object is not ordered with respect to the dynamic initialization of b.

The nothing at all possibility is because the initialization of b is not ordered with respect to the creation/initialization of std::cout; when you try to destroy the temporary, cout may not have been created/initialized yet, so attempting to print something may not work at that point at all. [Edit: this is specific to C++98/03, and does not apply to C++11.]

Edit: here is how I, at least, see the sequence:

enter image description here

Edit2: After rereading §12.2/4 (yet again), I've changed the diagram again. §12.2/4 says:

There are two contexts in which temporaries are destroyed at a different point than the end of the full expression. The first context is when an expression appears as an initializer for a declarator defining an object. In that context, the temporary that holds the result of the expression shall persist until the object’s initialization is complete. The object is initialized from a copy of the temporary; during this copying, an implementation can call the copy constructor many times; the temporary is destroyed after it has been copied, before or when the initialization completes.

I believe this expression is an initializer for a declarator defining an object, so it's required to initialize the object from a copy of the value of the expression (true, in this case), not directly from the return value. In the case of true, this is probably a distinction without a difference, but I think the diagram is technically more accurate as it stands right now.

This also makes fairly clear (I think) that the temporary holding true does not have to be destroyed at the end of the full expression, so I've re-drawn the diagram to reflect that as well.

This section is gone in C++0x/C++11, so I've re-drawn the diagram (yet again) to show the difference between the two (and how much simpler this piece has gotten in C++11).

原来分手还会想你 2024-11-10 05:55:22

(引用 C++03 标准)

首先是 §12.2/3:

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

我相信这是一个转移注意力的话题,因为 §1.9/13:

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

   (表达式列表)

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

    = 初始化子句

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


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

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

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

§1.9/16:

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

和§1.9/17:

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

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

(Quoting the C++03 standard)

First there's §12.2/3:

When an implementation introduces a temporary object of a class that has a non-trivial constructor (12.1), it shall ensure that a constructor is called for the temporary object. Similarly, the destructor shall be called for a temporary with a non-trivial destructor (12.4). Temporary objects are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created. This is true even if that evaluation ends in throwing an exception.

I believe this is a red herring, because of §1.9/13:

[Note: certain contexts in C++ cause the evaluation of a full-expression that results from a syntactic construct other than expression (5.18). For example, in 8.5 one syntax for initializer is

    ( expression-list )

but the resulting construct is a function call upon a constructor function with expression-list as an argument list; such a function call is a full-expression. For example, in 8.5, another syntax for initializer is

    = initializer-clause

but again the resulting construct might be a function call upon a constructor function with one assignment-expression as an argument; again, the function call is a full-expression. ]

This implies to me that A(b).yield() is itself a full expression, rendering §12.2/3 irrelevant here.

Then we get into sequence points -- §1.9/7:

Accessing an object designated by a volatile lvalue (3.10), modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment. Evaluation of an expression might produce side effects. At certain specified points in the execution sequence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place.

§1.9/16:

There is a sequence point at the completion of evaluation of each full-expression.

and §1.9/17:

When calling a function (whether or not the function is inline), there is a sequence point after the evaluation of all function arguments (if any) which takes place before execution of any expressions or statements in the function body. There is also a sequence point after the copying of a returned value and before the execution of any expressions outside the function.

Putting it all together, I think Clang is right and GCC (and MSVC 2010 SP1) is wrong -- the temporary that holds the result of the expression (whose lifetime is being extended as per §12.2/4) is the bool returned from A::yield(), not the temporary A on which yield is invoked. Taking into account §1.9, there should be a sequence point after the call to A::yield() during which the temporary A is destroyed.

巷雨优美回忆 2024-11-10 05:55:22

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


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

临时对象会被销毁
评估的最后一步
完整表达式 (1.9) that(词汇上)
包含他们所在的点
创建。

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

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

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

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


1

第一个上下文是当
表达式显示为初始值设定项
用于定义对象的声明符。
在这种情况下,临时
保存表达式的结果
应持续存在,直到该对象的
初始化完成”

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

First, just to clear up the paragraph that was previously here, using b in its own (dynamic) initialisation here is not UB. Before the expression is evaluated, b is not uninitialised but zero-initialised.


The temporary A must live for precisely as long as the full expression:

Temporary objects are destroyed as the
last step in evaluating the
full-expression (1.9) that (lexically)
contains the point where they were
created.

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

The line bool b = A(b).yield(); is a declaration, which is a statement, which is not an expression. The expression at hand is found only to the RHS of the =. [ISO/IEC 14882:2003(E) A.6]

This would mean that the temporary should be destroyed before the dynamic initialisation takes place, no? Sure, the value true is held in the temporary that contains the result of the expression1 until the initialisation completes, but the original A temporary should be destroyed before b is actually modified.

Therefore I'd expect the output false, every time.


1

The first context is when an
expression appears as an initializer
for a declarator defining an object.
In that context, the temporary that
holds the result of the expression
shall persist until the object’s
initialization is complete"

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文