【问题标题】:When exactly is an initializer temporary destroyed?初始化器究竟是什么时候被临时销毁的?
【发布时间】:2023-03-21 10:40:01
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 澄清一下,b 有静态存储?
  • @GMan 是的。我很抱歉这种不清楚。我将添加一个main 函数。
  • 我看到了这个问题,想起了我们在这个问题上的对话,并想“嗯,我会把这个链接发给 litb,看看他对这个问题有什么看法”。没关系,嗯? :)
  • main 中的赋值语句的情况下,Clang 会打印什么?
  • 有趣的是,Johannes Schaub - litb 得到这么多Upvotes 的问题,但答案却不多。哈哈..

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


【解决方案1】:

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

真假部分是(如您所说),临时 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)。

【讨论】:

  • +1(当我在 16 分钟内获得更多选票时)指出 cout 可能还不存在。
  • @Jerry 如果我做bool b = true; b = false; 这是否意味着我正在做未定义的行为?因为我没有看到对b 的写入被订购。在 C++03 中,它们似乎可能发生在相同的序列点之间,而在 C++0x 中,它们似乎是两个无序的副作用。
  • @litb:两个语句之间有一个序列点,不是吗?
  • @litb: 如果bool b = true; b = false; 是 UB,那么在我看来这是标准中的一个严重缺陷。
  • @Johannes:它说 (C++03, §1.9/16):“在每个完整表达式的评估完成时都有一个序列点。”再加上按顺序执行的语句,这似乎为您的 bool b=true; b=false; 示例定义了行为。 N3242 §1.9/14 使用不同的措辞,但提供基本相同的保证。这里的问题是我们有两件不同的事情需要发生(A() 的破坏和 b 的初始化),但彼此之间没有排序。
【解决方案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) 是从A::yield() 返回的bool,而不是调用yield 的临时A。考虑到§1.9,在调用A::yield() 之后应该有一个序列点,在此期间临时A 被销毁。

【讨论】:

    【解决方案3】:

    首先,只是为了清除之前在这里的段落,在这里使用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]

    【讨论】:

    • "我意识到 b 在指定的初始化发生之前被零初始化为 false。无论如何,在它自己的初始化程序中使用它是否仍然是 UB?" :否, 因为b 具有静态存储持续时间,所以在 动态 初始化期间引用它自己是可以的,因为它保证已经被零初始化。如果b 是在本地范围内定义/初始化的(例如在main 内部),或者如果它是用一个引用自身的常量表达式初始化的,那么它将是UB。
    • 啊,这很有意义。但我认为它并没有说明b 的初始化必须在完整表达式序列点之前或之后发生。如果实现在yield() 返回之后直接初始化b,并且在销毁临时A 之前会怎样?临时仍然会被销毁“作为评估完整表达式的最后一步”。
    • @ildjarn:我知道它已经有了零初始化的值,但我似乎找不到任何关于这个 UB 的子句说它对动态初始化有区别。我可能只是想念它。还在寻找。
    • @Tomalak Geret'kal :对我来说,这是 §3.6.2/1 的措辞所暗示的
    • @ildjarn:我不同意,但我确实认为,因为在讨论这种情况的所有情况下,术语就像“使用未初始化的对象”,我们可以将“未初始化”表示“静态未初始化” " 如果对象是静态的。
    猜你喜欢
    • 2023-03-31
    • 2015-10-12
    • 1970-01-01
    • 1970-01-01
    • 2011-08-19
    • 1970-01-01
    • 1970-01-01
    • 2021-08-03
    • 1970-01-01
    相关资源
    最近更新 更多