为什么链接需要运算符到 return 引用?

Why chaining requires operator to return reference?

应该return参考的最流行的运算符是operator=

class Alpha
{
    int x;
    int y;
    std::string z;
  public:
    void print()
        { cout << "x " << x << " y " << y << "  " << z << '\n'; }
    Alpha(int xx=0, int yy=0, std::string zz=""): x(xx), y(yy), z(zz) {}
    Alpha operator=(Alpha& another)
    {
        x = another.x;
        y = another.y;
        z = another.z;
        return *this;
    }


};

int main()
{
    Alpha a(3,4,"abc"),b,c;
    b=c=a;
    return 0;
}

Clang 是这样说的:

clang++-3.6 new.cxx -o new new.cxx:70:3: error: no viable overloaded '=' b=c=a; ~^~~~ new.cxx:34:8: note: candidate function not viable: expects an l-value for 1st argument Alpha operator=(Alpha& another) ^ 1 error generated.

gcc 这个:

new.cxx:34:8: note: no known conversion for argument 1 from ‘Alpha’ to ‘Alpha&’

但是我无法理解理论上有什么问题。我认为会发生什么:

  1. 首先为对象 c 调用了 operator=。它通过引用接收对象 a,将其值复制到 c 和 return 自身(对象 c 的)的匿名副本:调用复制 soncstructor。
  2. 然后为对象 b 调用 operator=。它需要右值引用,但我们只写了左值引用,所以出现错误。

我添加了 rval operator= 和复制构造函数,它接收左值引用并且一切正常,现在我不知道为什么(我应该编写接收 const Alpha& sAlpha&& s 的右值复制构造函数):

class Alpha
{
    int x;
    int y;
    std::string z;
  public:
    void print()
    { cout << "x " << x << " y " << y << "  " << z << '\n'; }
    Alpha(int xx=0, int yy=0, std::string zz=""): x(xx), y(yy), z(zz) {}
    //Alpha(Alpha&& s): x(s.x), y(s.y), z(s.z) {}
    Alpha(Alpha&& ) = delete;
    Alpha(Alpha& s): x(s.x), y(s.y), z(s.z) {}
    Alpha operator=(Alpha& another)
    {
        x = another.x;
        y = another.y;
        z = another.z;
        return *this;
    }
    Alpha operator=(Alpha&& another)
    {
        x = another.x;
        y = another.y;
        z = another.z;
        return *this;
    }

};

此行为是故意的,让我们有机会微调代码:

  • 在第一个片段中,您明确定义了 operator=() 以需要左值引用。正如在你的链中你不提供这样的参考,编译器抱怨。

  • 在第二个片段中,您添加了 operator=() 的重载以接受右值引用。所以这可以在不破坏链的情况下按值处理 return 。

在您的特定情况下,两种备选方案都具有完全相同的代码,这就是您不理解问题的原因。但是对于更复杂的数据结构,具有不同的行为可能是完全有意义的。想象一下,您的 class 中有一个包含数千个元素的向量成员:

class Alpha {
    vector<double> hugevec; 
    ...
};

在引用赋值的情况下,您会照常进行(因为原始对象必须保持活动状态):

Alpha operator=(Alpha& another)
{
    ...
    hugevec = another.hugevec;  // thousands of elements get duplicated
    return *this;
}

但是在从临时值对象赋值的情况下,您可以 "steal" 现有向量,因为它无论如何都会被丢弃(您也可以重用它的指针成员之一,而不是分配一个新对象复制它,并销毁旧的):

Alpha operator=(Alpha&& another)
{
    ...
    swap (another.hugevec);  // no elements get duplicated !
                             // the old elements are now in the temporary 
                             // which will take care of their destruction
    return *this;
}

因此,编译器做出的这种细微差别可以显着提高性能。

顺便说一下,这不应与使用相同 && 语法但在模板中的通用引用相混淆,后者让编译器选择最合适的右值或左值引用。

编辑:您可能对以下文章感兴趣:

这个赋值运算符的签名

Alpha operator=(Alpha& another)

两个方面是不寻常的。首先是它 return 是分配给对象的副本。很少这样做。另一个是它接受一个非常量引用作为参数。

非 const 引用使其不接受临时对象作为参数(因为它们只会绑定到 const 左值引用)。

结合起来,这意味着从第一个 operator= 临时 returned 不能用作第二个 operator= 的参数。

您的选择是 return 一个参考,或者使参数 Alpha const&。这两个选项可以单独使用,也可以组合使用。

正如您发现的,第三个选项是显式添加移动赋值运算符,使用 Alpha&& 专门接受临时对象。

虽然标准方法是声明复制赋值运算符

Alpha& operator=(Alpha const& other);

除非您有非常选择其他签名的具体原因。