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

时间:2019-04-08 07:09:01

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

在下面的简单示例中,为什么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

4 个答案:

答案 0 :(得分:38)

这是设计使然。简而言之,只有与 直接绑定的命名引用会延长其寿命。

  

[class.temporary]

     

5在三种情况下,   与完整表达的结束点不同。 [...]

     

6第三种情况是引用绑定到临时项时。   引用绑定到的临时对象或   引用绑定到的子对象的完整对象   在参考的有效期内持续存在,除了:

     
      
  • 绑定到函数调用中的参考参数的临时对象将持续存在,直到包含以下内容的完整表达式完成   电话。
  •   
  • 在函数return语句中,临时绑定到返回值的生存期不会延长;临时被毁   在return语句的完整表达式的末尾。
  •   
  • [...]
  •   

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

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

答案 1 :(得分:16)

这是故意的。只有将引用直接绑定到该临时 时,该引用才能延长其使用寿命。在您的代码中,您将mergedArrays绑定到ref2的结果上,该结果是一个引用。该引用引用了一个临时对象都没关系。只有min延长了临时文件的生命周期; b也指相同的临时变量都没关系。

另一种查看方式:您不能选择有生命周期延长。这是一个静态属性。如果ref2将执行正确的事情 tm ,则取决于ref2x的运行时值,寿命是否延长。编译器无法执行的操作。

答案 2 :(得分:8)

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

  

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

     
      
  • 临时实现转换([conv.rval]),
  •   
  • ( 表达式 ),其中expression是这些表达式之一,
  •   
  • 对数组操作数进行下标([expr.sub]),其中该操作数是这些表达式之一,
  •   
  • 使用.运算符的类成员访问([expr.ref]),其中左操作数是这些表达式之一,而右操作数指定非引用类型的非静态数据成员,
  •   
  • 使用.*运算符的指向成员的指针操作([expr.mptr.oper]),其中左操作数是这些表达式之一,而右操作数是指向非引用数据成员的指针类型
  •   
  • a 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是从函数调用获得的,而函数调用不是生存期扩展的允许表达式之一。 / p>

绑定到参考参数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;

但是,在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情况下延长生命周期。

答案 3 :(得分:0)

[由于实际上非constexpr版本已编译,因此应该更新答案。

constexpr

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

说明:y + 1产生的 rvalue 的生存期实际上已延长。这是因为return的{​​{1}}类型是引用常量,即min,并且只要您有引用常量的直接绑定到< em> rvalue 类型,基础const T&值的生存期会延长,直到存在 reference-to-const

进一步,rvalue的对常量的引用输出随后直接分配给类型为min的左值名称ref2const int&类型也应该在这里工作(即int),在这种情况下,基础 rvalue 将被复制并破坏对const的引用。

总之,至少在符合现代C ++标准的最新版本的编译器中,非int ref2 = min(x, y+1);版本应始终产生所需的输出。

constexpr版本

这里的问题有所不同,因为constexpr的类型说明符要求它是一个ref2,这反过来又要求表达式是编译时文字。尽管从理论上讲,可以在此处将生命周期扩展应用于constexpr引用常量类型,但是C ++尚不允许它(即,它不创建临时的constexpr类型来保存基础 rvalues ),也许是因为它禁止进行某些优化或使编译器的工作更加困难-不确定是哪一种。

但是,您应该可以通过以下方法轻松解决此问题:

constexpr