xvalue 的生命周期是否绑定到引用扩展?

use*_*108 5 c++ language-lawyer c++11

显然,关于这个问题,编译器之间存在一些混淆和差异:

http://social.msdn.microsoft.com/Forums/vstudio/en-US/3c754c4e-5471-4095-afae-795c1f411612/rvalue-refs-extended-lifetime-inconsistent-with-gccstandard

根据这篇文章:

什么是右值、左值、xvalues、glvalues 和 prvalues?

Xvalues 是 rvalues(与 prvalues 一起),标准说:

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

然而,有一些帖子对此提出异议:

右值引用允许悬空引用吗?

什么是非 POD 对象的 xvalue 和 prvalue 之间允许的使用或行为差异的示例?

有人可以澄清这个问题。MSVC 适合一次吗?

Nic*_*las 3

X值可能是右值,但这并不意味着它们是临时的临时对象的生命周期延长来自于它们是临时对象的事实,而不是它们的值类别。

我故意尝试不知道运算符的处理顺序(这样,我强迫自己编写使用显式括号或不关心顺序的代码)。您的具体adder示例代码(在此处复制)确实关心:

template <class T>
struct addable
{
    friend T operator +( const T& lhs, const T& rhs )
    {
        return std::move(T(lhs) += rhs); 
    }
    friend T operator +( const T& lhs, T&& rhs )
    {
        return std::move(T(lhs) += std::move(rhs));
    }   
    friend T&& operator +( T&& lhs, const T& rhs ) 
    {
        return std::move(lhs += rhs);
    }               
    friend T&& operator +( T&& lhs, T&& rhs ) 
    {
        return std::move(lhs += std::move(rhs)); 
    }
};
Run Code Online (Sandbox Code Playgroud)

如果+运算符是从右到左执行的,那么t1 + t2 + t3将计算出t1 + (t2 + t3)t2 + t3将调用第一个重载,从而产生一个临时的,从而产生t1 + temp。由于临时变量将优先绑定到右值引用,因此该表达式将调用第二个重载,该重载将返回临时变量。

但是,如果+运算符从左到右工作,那么您会得到(t1 + t2) + t3。这给了我们temp + t1,这导致了一个问题。它将调用第三次重载。该函数的参数lhs是 a T&&,对临时变量的引用。您返回相同的引用。这意味着您已经返回了对临时对象的引用。但 C++ 不知道这一点;它只知道您正在返回对某些内容的引用。

然而,这个“东西”将在最终表达式(对新变量的赋值,无论是值类型还是引用类型)被评估后被销毁。请记住:C++ 不知道该函数将返回对其第一个参数的引用。因此它无法知道传递给函数操作数的临时生命周期是否需要延长到存储返回引用的生命周期。

顺便说一句,这就是为什么表达式树可能很危险,auto并且潜伏在其中。因为创建的内部临时对象无法通过存储在各种对象中的新临时对象或引用来保留。C++ 只是没有办法做到这一点。

因此,谁是正确的取决于运算符的解析顺序。但是,我更喜欢我的解决方案:不要依赖语言的这些角落,只需解决它们即可。停止从这些重载中返回T&&,并将值移至临时值。这样,就可以保证它正确工作,并且您不必不断检查标准以确保您的代码正常工作。

另外,顺便说一句,我认为运算符+实际修改其中一个参数有点粗鲁。

然而,如果你坚持要知道谁是对的,那就是 GCC。从第 5.7 节第 1 页:

加法运算符 + 和 - 从左到右分组。

所以是的,它不应该起作用。

注意:Visual Studio 允许T &r2 = t1 + t2 + t3;编译为(非常烦人的)语言扩展。希望你已经收到了警告。

  • 不。“t1 + t2”将具有完整的表达式生命周期...通过引用函数参数绑定不会限制其生命周期... (2认同)