【问题标题】:Creating a reference out of multiple temporaries从多个临时对象中创建引用
【发布时间】:2014-11-30 11:35:32
【问题描述】:

我正在尝试用户定义的文字(我相信是在 gcc 4.7.1 中引入的),但对临时变量的工作方式感到困惑。

考虑以下代码:

#include <stdlib.h>
#include <iostream>

class Point
{
public:    
    Point(float x = 0, float y = 0, float z = 0)
    :_x(x), _y(y), _z(z) 
    {
        std::cout << "Constructor\n";    
    }
    ~Point()
    {
        std::cout << "Destructor\n";
    }
    Point& operator=(Point p)
    {
        std::cout << "Assignment op\n";
        return *this;
    }

    Point& operator+(const Point& p)
    {
        std::cout << "Returning ref: operator+\n";
       _x += p._x; 
       _y += p._y; 
       _z += p._z; 
       return *this;
    }

    void print() const
    {
        std::cout << "(" << _x << ", " << _y << ", " << _z <<")\n";
    }

protected:
    float _x, _y, _z;
};

Point operator "" _x(const char* l)
{
    float x = atof(l);
    std::cout<<"literal _x\n";
    Point p(x);
    return p;
}

Point operator "" _y(const char* l)
{
    float y = atof(l);
    std::cout<<"literal _y\n";
    Point p(0, y);
    return p;
}

Point operator "" _z(const char* l)
{
    float z = atof(l);
    std::cout<<"literal _z\n";
    Point p(0, 0, z);
    return p;
}

int main(int argc, char **argv)
{
    Point& p = 12_x + 2_x + 3_y + 4_z;
    p.print();
}

执行后我得到以下输出:

literal _z
Constructor
literal _y
Constructor
literal _x
Constructor
literal _x
Constructor
Returning ref: operator+
Returning ref: operator+
Returning ref: operator+
Destructor
Destructor
Destructor
Destructor
(14, 3, 4)

当我将行更改为Point&amp; p = 12_x; 时,会发出一个错误,提示您无法从右值初始化引用。

当我将其更改为 const Point&amp; p = 12_x; 时,我得到:

literal _x
Constructor
(12, 0, 0)
Destructor

我希望这两种情况中的任何一种,而不是第一种情况,所以我的问题是:第一种情况到底会发生什么?

附:我正在使用带有 gcc 4.8.1 的 MinGW。编译字符串:g++ --std=c++11 -Wall main.cpp -o out.exe

【问题讨论】:

  • 引用本身没有存储空间(只有它所引用的地址)。因此,您不能引用表达式(右值)。为了解决这个问题,你必须产生一个变量Point pp = ...,然后是Point &amp;p = pp;
  • [OT]:你现在可以使用--std=c++11了。
  • 您可以通过将您的运算符声明为 @987654331 来防止您的 operator+ 被应用于临时对象(这样它就不会将 非 const 左值引用返回到临时对象) @(即末尾多出一个 & 号)
  • @PiotrS。或添加Point&amp; operator+(const Point&amp;&amp;) = delete;
  • @JonathanWakely:但这将不允许:Point&amp; p = lvalue_of_point + 2_x;。但是,它是否应该起作用是有争议的,因为这里的主要问题是operator+ 是根据operator+= 实现的

标签: c++ gcc c++11 mingw


【解决方案1】:

您的 + 运算符签名不正常,它应该返回一个新的临时而不是对 LHS 的引用。你实际上已经实现了 +=。

这就是为什么您的加号运算符将绑定到非常量引用。

虽然您不能将临时对象绑定到非常量引用,但您可以对其执行非常量操作。因此,当第一个术语实际上是临时的时,它将编译您的 + 运算符。

即使swap() 是一个非常量函数,您也可以使用vector&lt;int&gt;().swap( myVec ) 来清除myVec 的内存。

当您想在单行中创建字符串构建函数并且在对象下方使用成员 ostringstream 上的 operator&lt;&lt; 时,它对于字符串构建函数也很有用。事实证明这是安全的,因为在这种情况下您进行的最终调用是 str() 调用,它返回的值不是成员引用。

在你的情况下,你也可以在你的实现中使用这个构造 operator+

class Point
{
    public:
      Point & operator+=( const Point& ); // as you implemented +

      Point operator+( const Point & rhs ) const
      {
        return Point( *this ) += rhs;
      }
};

【讨论】:

  • 虽然我理解了其余部分,但我已经阅读了第一句话几次,但不确定我是否完全理解它。您能否详细说明“它应该替换一个新的临时而不是对 LHS 的引用”的意思?我们是否要替换一个对象,其中operator+() 被调用?
  • 我的意思是它应该返回一个新的临时文件,替换 LHS。
【解决方案2】:

在这一行:

Point& p = 12_x + 2_x + 3_y + 4_yz;

您会获得对临时对象的引用,该对象在执行该行后立即被销毁,从而导致未定义的行为。您实际上可以在您的日志中看到:调用了四个构造函数,然后调用了四个析构函数 - 所以所有创建的对象都在调用print()之前被销毁

当你写作时:

const Point& p = 12_x;

根据12.2/5 ([class.temporary]),它会导致12_x 返回的临时对象的生命周期延长,因此仅当该引用超出范围时才调用析构函数,即这一次在您调用print() 之后。

PS:摘自12.2/412.2/5

在两种情况下,临时对象在与完整表达式结尾不同的点被销毁。 (...) 第二个上下文是引用绑定到临时的。 (...) [ 示例:

struct S {
    S();
    S(int);
    friend S operator+(const S&, const S&);
    ~S();
};
S obj1;
const S& cr = S(16)+S(23);
S obj2;

(...) 绑定到引用cr 的临时T3cr 的生命周期结束时被销毁,即在程序结束时。 (...)

顺便说一句,这里的示例还提供了实现operator+ 的正确方法(除了您甚至可能不需要它是friend)。你在代码中写的应该是operator+=

【讨论】:

    【解决方案3】:

    表达式12_x创建了一个临时对象,创建一个临时对象的引用显然是行不通的,当临时对象被破坏时引用的是什么(一旦它所在的表达式完成了它就会是什么)?

    但是,只要引用变量在范围内,创建 const 引用就会导致编译器延长临时对象的生命周期。

    带有添加的较长表达式也会创建临时对象,但由于 operator+ 函数被定义为返回引用,因此整个表达式的结果是引用。不幸的是,它将是一个临时对象的引用,使用它会导致undefined behavior,所以你很幸运它可以工作。


    正如 Potatoswatter 所指出的,右值引用应该与临时对象一起使用。这是 C++11 中引入的一种新的引用类型,用双 & 符号表示,如

    Point&& p = 12_x;
    

    【讨论】:

    • 我会 +1,只是它不是那么“明显”。右值引用也可以。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-01
    • 2016-01-30
    • 1970-01-01
    相关资源
    最近更新 更多