为什么const引用不能延长通过函数传递的临时对象的寿命?

Khu*_*dov 45 c++ language-lawyer temporary-objects c++11

在下面的简单示例中,为什么不能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
}
Run Code Online (Sandbox Code Playgroud)

现场示例 -产生:

main:
  xor eax, eax
  ret
Run Code Online (Sandbox Code Playgroud)

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

#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;
}
Run Code Online (Sandbox Code Playgroud)

现场示例产生:

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

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

                                      ^

Compiler returned: 1
Run Code Online (Sandbox Code Playgroud)

Sto*_*ica 39

这是设计使然。简而言之,只有直接绑定了临时名称的命名引用才能延长其寿命。

[class.temporary]

5在三种情况下,临时性销毁的时间与正式表达的结束时间不同。[...]

6第三种情况是引用绑定到临时项时。引用绑定到的临时对象或引用绑定到的子对象的完整对象的临时对象在引用的生存期内一直存在,但以下情况除外:

  • 绑定到函数调用中参考参数的临时对象将一直保留,直到包含该调用的完整表达式完成。
  • 在函数return语句中,临时绑定到返回值的生存期不会延长;临时在return语句中的全表达式结束时销毁。
  • [...]

您没有直接绑定到ref2,甚至还通过return语句传递了它。该标准明确表示不会延长使用寿命。在某种程度上使某些优化成为可能。但是最终,因为很难理解在传入和传出函数时应该扩展哪个临时对象。

由于编译器可能会在您的程序没有任何未定义行为的假设下进行积极的优化,因此您可能会看到这种情况。未定义在其生存期之外访问值的操作,这是未定义的return ref2; 操作,并且由于该行为是未定义的,因此简单地返回零是要显示的有效行为。编译器不会破坏任何合同。


Rak*_*111 17

这是故意的。当引用直接绑定到该临时对象时,它只能延长该临时对象的寿命。在您的代码中,您绑定ref2min作为参考的结果。该引用引用了一个临时对象都没关系。仅b延长了临时文件的寿命;没关系,ref2也指那个相同的临时。

另一种看待它的方式:您不能选择有生命周期延长。这是一个静态属性。如果ref2将正确的做法TM,然后视的运行值xy+1寿命延长或不。编译器无法执行的操作。


Bri*_*ian 8

我将首先回答这个问题,然后为回答提供一些背景信息。当前的工作草案包含以下措词:

如果绑定引用的glvalue是通过以下方式之一获得的,则引用绑定到的临时对象或作为引用绑定到的子对象的完整对象的临时对象在引用的生存期内将持续存在:

  • 临时物化转换([conv.rval]),
  • ( expression ),其中expression是这些表达式之一,
  • 下标([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])转换,没有一个用户定义的转换,作为这些表达式之一的glvalue操作数,该glvalue引用由该操作数指定的对象或其完整对象或其子对象,
  • 条件表达式([expr.cond])是glvalue,其中第二个或第三个操作数是这些表达式之一,或者
  • 一个逗号表达式([expr.comma]),它是glvalue,其中正确的操作数是这些表达式之一。

据此,当引用绑定到从函数调用返回的glvalue时,不会发生生存期扩展,因为glvalue是从函数调用获得的,而函数调用不是生存期扩展的允许表达式之一。

y+1当绑定到引用参数时,临时生存期将延长一次b。在这里,prvalue y+1被实例化以产生一个xvalue,并且引用被绑定到临时实例化转换的结果。因此发生寿命延长。min但是,当函数返回时,ref2将绑定到调用的结果,并且此处不会发生生存期延长。因此,y+1临时对象在的定义末尾被破坏ref2,并ref2成为悬挂的参考。


历史上一直对此主题有些困惑。众所周知,OP的代码和类似的代码会导致悬挂的引用,但是即使是C ++ 17版本的标准文本也没有提供明确的解释。

通常声称寿命延长仅在引用“直接”绑定到临时文件时才适用,但是该标准从未对此表示任何意见。实际上,该标准定义了引用“直接绑定”的含义,并且该定义(例如const std::string& s = "foo";是间接引用绑定)在这里显然无关紧要。

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

struct S { int x; };
const int& r = S{42}.x;
Run Code Online (Sandbox Code Playgroud)

但是,在C ++ 14中,表达式S{42}.x变为xvalue,因此,如果在此处应用生命周期扩展,则不是因为引用绑定到prvalue。

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

StoryTeller在这里还说过,引用必须直接绑定,但是我也不知道他的意思。他引用了标准文本,该文本表明在return语句中将引用绑定到临时项不会延长其寿命。但是,该语句似乎适用于该return语句由语句中的全表达式创建的情况,因为它表示该临时语句将在该全表达式结束时销毁。显然,y+1临时情况并非如此,临时情况会在包含调用的完整表达式的末尾销毁min。因此,我倾向于认为该陈述并不打算适用于类似问题的情况。取而代之的是,它的作用以及对生存期扩展的其他限制,是为了防止任何临时对象的生存期扩展到其创建时所处的块范围之外。但这不会阻止问题中的y+1临时词生存到年底main

因此,问题仍然存在:什么是原理解释为什么ref2问题中与临时对象的绑定不会延长该临时对象的寿命?

我早些时候引用的当前工作草案中的措词是由CWG 1299决议引入的,该决议于2011年开放,但直到最近才得到解决(对于C ++ 17而言这不是及时的)。从某种意义上讲,它通过描述绑定“直接”足够足以延长生命周期的情况来阐明直觉,即引用必须“直接”绑定;但是,它没有那么严格的限制,只允许在引用绑定到prvalue时才允许它。在这种S{42}.x情况下,可以延长使用寿命。