【问题标题】:Why doesn't a const reference extend the life of a temporary object passed via a function?为什么 const 引用不能延长通过函数传递的临时对象的寿命?
【发布时间】:2019-08-29 06:43:34
【问题描述】:

下面这个简单的例子,为什么ref2不能绑定min(x,y+1)的结果?

#include <cstdio>
template< typename T > const T& min(const T& a, const T& b){ return a < b ? a : b ; }

int main(){
      int x = 10, y = 2;
      const int& ref = min(x,y); //OK
      const int& ref2 = min(x,y+1); //NOT OK, WHY?
      return ref2; // Compiles to return 0
}

live example - 产生:

main:
  xor eax, eax
  ret

编辑: 我认为下面的示例更好地描述了一种情况。

#include <stdio.h>


template< typename T >
constexpr T const& min( T const& a, T const& b ) { return a < b ? a : b ; }



constexpr int x = 10;
constexpr int y = 2;

constexpr int const& ref = min(x,y);  // OK

constexpr int const& ref2 = min(x,y+1); // Compiler Error

int main()
{
      return 0;
}

live example 产生:

<source>:14:38: error: '<anonymous>' is not a constant expression

 constexpr int const& ref2 = min(x,y+1);

                                      ^

Compiler returned: 1

【问题讨论】:

  • 此程序不产生任何输出并以代码 0 退出。带有-O3 优化标志的 main 内的所有语句都将被丢弃。
  • 它给出了什么错误?
  • 这其实是一个很有趣的问题。我会标记语言律师,并希望其中一位酋长接手这个。我既没有时间也没有专业知识。这一切都与生命周期扩展不具有传递性以及原始对象所在的位置有关。继续@StoryTeller。
  • @Bathsheba 你能快速解释一下为什么b &lt; a ? b : a 这么有优势吗?
  • @Michiel 问题是,如果绑定是直接的(例如,如果 min 按值返回),ref2 会将绑定临时的生命周期延长到自身的生命周期。因此,我猜是这个问题。

标签: c++ c++11 language-lawyer temporary-objects


【解决方案1】:

这是设计使然。简而言之,只有 named 临时绑定到的引用 直接 会延长其生命周期。

[class.temporary]

5 临时对象在三种情况下被销毁 与完整表达式的结尾不同的点。 [...]

6 第三个上下文是引用绑定到临时的。 引用绑定到的临时对象或被绑定的临时对象 引用绑定到的子对象的完整对象 在引用的生命周期内持续存在,除了:

  • 在函数调用中绑定到引用参数的临时对象将持续存在,直到包含 来电。
  • 临时绑定到函数返回语句中的返回值的生命周期未延长;临时被毁 在 return 语句中的完整表达式的末尾。
  • [...]

您没有直接绑定到ref2,甚至通过return 语句传递它。该标准明确表示它不会延长使用寿命。部分是为了使某些优化成为可能。但归根结底,因为当引用传入和传出函数时,通常很难跟踪应该扩展哪个临时对象。

由于编译器可能会在您的程序没有表现出未定义行为的假设下进行积极优化,因此您会看到这种情况的可能表现。访问其生命周期之外的值是未定义的,这就是return ref2; 所做的,并且由于行为未定义,简单地返回零是一种有效的行为。编译器不会破坏任何合约。

【讨论】:

  • 非常感谢。我错误地认为,y+1 临时对象绑定到b 并且它是通过返回函数实时扩展的。 .
【解决方案2】:

这是故意的。只有当引用直接绑定到临时临时对象时,引用才能延长临时对象的生命周期。在您的代码中,您将ref2 绑定到min 的结果,这是一个参考。该引用引用一个临时的并不重要。只有b 延长了临时的生命周期; ref2 也指同一个临时文件并不重要。

另一种看待它的方式:您不能选择延长生命周期。这是一个静态属性。如果ref2 会做正确的事情tm,那么取决于xy+1 的运行时值,生命周期是否延长。不是编译器能够做到的。

【讨论】:

    【解决方案3】:

    我会先回答这个问题,然后提供一些背景信息。 The current working draft contains the following wording:

    如果引用绑定的glvalue是通过一个获得的,那么引用绑定到的临时对象或作为引用绑定到的子对象的完整对象的临时对象将在引用的生命周期内持续存在以下内容:

    • 临时实现转换 ([conv.rval]),
    • ( 表达式 ),其中表达式是这些表达式之一,
    • 数组操作数的下标 ([expr.sub]),其中该操作数是这些表达式之一,
    • 使用. 运算符的类成员访问 ([expr.ref]),其中左操作数是这些表达式之一,右操作数指定非引用类型的非静态数据成员,
    • 使用.* 运算符的指向成员操作([expr.mptr.oper]),其中左操作数是这些表达式之一,右操作数是指向非引用类型的数据成员的指针,
    • const_­cast ([expr.const.cast])、static_­cast ([expr.static.cast])、dynamic_­cast ([expr.dynamic.cast]) 或 reinterpret_­cast ([expr. .reinterpret.cast]) 在没有用户定义的转换的情况下,将作为这些表达式之一的泛左值操作数转换为指代操作数指定的对象或其完整对象或其子对象的泛左值,
    • 一个条件表达式 ([expr.cond]),它是一个泛左值,其中第二个或第三个操作数是这些表达式之一,或者
    • 一个逗号表达式 ([expr.comma]),它是一个右操作数是这些表达式之一的左值。

    据此,当引用绑定到从函数调用返回的glvalue时,不会发生生命周期延长,因为glvalue是从函数调用中获得的,这不是生命周期延长的允许表达式之一。

    y+1 临时对象的生命周期在绑定到引用参数b 时会延长一次。这里,prvaluey+1被物化产生一个xvalue,引用绑定到临时物化转换的结果;寿命延长因此发生。但是,当min 函数返回时,ref2 会绑定到调用结果,并且此处不会发生生命周期延长。因此,y+1 临时在ref2 的定义结束时被销毁,ref2 成为悬空引用。


    这个话题在历史上一直存在一些混淆。众所周知,OP 的代码和类似代码会导致引用悬空,但标准文本,即使在 C++17 中,也没有明确解释原因。

    通常声称延长生命周期仅适用于引用“直接”绑定到临时对象时,但标准从未说明过这种情况。实际上,该标准定义了“直接绑定”引用的含义,并且该定义(例如const std::string&amp; s = "foo"; 是间接引用绑定)显然与此处无关。

    Rakete1111 在 SO 其他地方的评论中说过,生命周期延长仅适用于引用绑定到纯右值(而不是通过先前的引用绑定到该临时对象获得的某些左值);他们似乎在这里通过“绑定......直接”说了类似的话。然而,这一理论没有文字支持。实际上,有时会考虑以下代码来触发生命周期延长:

    struct S { int x; };
    const int& r = S{42}.x;
    

    但是,在 C++14 中,表达式 S{42}.x 变成了一个 xvalue,所以如果在这里应用生命周期扩展,那么这并不是因为引用绑定到了一个纯右值。

    相反,人们可能会声称生命周期延长只适用一次,并且将任何其他引用绑定到同一对象不会进一步延长其生命周期。这可以解释为什么 OP 的代码会创建一个悬空引用,而不会阻止 S{42}.x 情况下的生命周期延长。但是,标准中也没有说明这一点。

    StoryTeller 在这里也说过引用必须直接绑定,但我也不知道他的意思。他引用了标准文本,表明在return 语句中绑定对临时的引用不会延长其生命周期。但是,该语句似乎旨在适用于由return 语句中的完整表达式创建的临时对象的情况,因为它表示临时对象将在该完整表达式的末尾被销毁。显然,y+1 临时不是这种情况,它将在包含对min 的调用的完整表达式的末尾被销毁。因此,我倾向于认为这种说法并不打算适用于问题中的这种情况。相反,它的效果以及生命周期延长的其他限制是防止任何临时对象的生命周期超出创建它的块范围。但这不会阻止问题中的y+1 临时存在直到main 结束。

    因此问题仍然存在:解释为什么将ref2 绑定到问题中的临时文件不会延长该临时文件的生命周期的原理是什么?

    我之前引用的当前工作草案中的措辞是由CWG 1299 的决议引入的,该决议于 2011 年开放,但最近才解决(不及时用于 C++17)。从某种意义上说,它阐明了引用必须“直接”绑定的直觉,通过描述绑定足够“直接”以发生生命周期延长的情况;但是,它并没有限制到仅在引用绑定到纯右值时才允许它。它允许在 S{42}.x 情况下延长生命周期。

    【讨论】:

      【解决方案4】:

      [答案应该更新,因为非constexpr版本实际上编译]

      constexpr版本

      演示: https://godbolt.org/z/_p3njK

      解释:y + 1 产生的rvalue 的生命周期实际上被延长了。这是因为 minreturn 类型是对 const 的引用,即 const T&amp; 并且每当你有一个对 const 的引用 直接 到一个 rvalue 类型,基础rvalue 值的生命周期被延长,直到 reference-to-const 存在。

      更进一步,min 的引用到常量输出然后直接分配给类型为const int&amp; 的左值名称ref2int 类型也应该在这里工作(即int ref2 = min(x, y+1);),在这种情况下,底层的 rvalue 将被复制并且对 const 的引用将被破坏。

      总之,至少在符合现代 C++ 标准的最新版本编译器中,非constexpr 版本应始终产生所需的输出。

      constexpr 版本

      这里的问题有所不同,因为 ref2 的类型说明符要求它是 constexpr ,这反过来又要求表达式是编译时文字。虽然理论上可以在这里为constexprreference-to-const 类型应用生命周期延长,但C++ 还不允许这样做(即它不会创建临时的constexpr 类型来保存底层rvalues em>),可能是因为它禁止了一些优化或使编译器的工作更加困难——不确定是哪一个。

      但是,您应该能够通过以下方式轻松解决这种情况:

      constexpr int value = min(x, y + 1);
      constexpr int const& ref2 = value;
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-02-05
        • 1970-01-01
        相关资源
        最近更新 更多