6

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

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

根据这篇文章:

什么是 rvalues、lvalues、xvalues、glvalues 和 prvalues?

Xvalues 是 rvalues(连同 prvalues),标准说:

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

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

右值引用是否允许悬空引用?

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

有人可以澄清这个问题。MSVC 一次就对了吗?

4

1 回答 1

3

Xvalues 可能是 rvalues,但这并不意味着它们是temporaries。临时人员的寿命延长来自于他们是临时人员这一事实,而不是他们的价值类别。

我故意不知道处理运算符的顺序(这样,我强迫自己编写使用显式括号或不关心顺序的代码)。您在此处复制的特定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)); 
    }
};

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

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

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

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

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

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

但是,如果您坚持要知道谁是对的,那就是 GCC。从第 5.7 节 p1 开始:

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

所以是的,它不应该工作。

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

于 2013-07-21T03:40:56.960 回答