为什么链接需要运算符返回引用

Why chaining requires operator to return reference?

本文关键字:返回 引用 运算符 链接 为什么      更新时间:2023-10-16

应返回引用的最流行的运算符是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++-3.6 new.cxx -o new new.cxx:70:3:错误:没有可行的重载 '=' b=c=a; ~^~~~ new.cxx:34:8:注意:候选函数不可行:第一个参数需要 l 值 Alpha 运算符=(Alpha & other) ^ 生成 1 个错误。

海湾合作委员会这个:

new.cxx:34:8:注意:参数 1 没有从"Alpha"到"Alpha&"的已知转换

但我不明白理论上有什么问题。我认为会发生什么:

    首先,为
  1. 对象 c 调用运算符 = 。它通过引用接收对象a,将其值复制到 c 并返回其自身(对象 c 的匿名副本):调用复制 soncstructor。
  2. 然后为对象 b 调用运算符 = 。它需要右值引用,但我们只写了左值引用,所以会发生错误。

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

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;
    }
};

值运算符的此签名

Alpha operator=(Alpha& another)

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

非常量引用

使其不接受临时对象作为参数(因为这些对象只会绑定到常量左值引用)。

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

您的选项是返回引用,或使参数Alpha const& 。这两个选项都可以单独工作,也可以组合使用。

如您所发现的,第三个选项是显式添加移动分配运算符,使用专门接受临时运算符的Alpha&&

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

Alpha& operator=(Alpha const& other);

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

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

  • 在第一个代码段中,您明确定义了需要左值引用operator=()。 就像在你的链中你没有提供这样的引用一样,编译器抱怨。

  • 在第二个代码段中,您添加了接受右值引用的重载 operator=()。 因此,这可以处理按值返回而不会破坏链。

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

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

在引用赋值的情况下,您可以像往常一样执行(因为原始对象必须保持活动状态):

Alpha operator=(Alpha& another)
{
    ...
    hugevec = another.hugevec;  // thousands of elements get duplicated
    return *this;
}
但是,如果从临时值对象

赋值,您可以"窃取"现有向量,因为它无论如何都会被丢弃(您也可以重用其指针成员之一,而不是分配新对象并复制它,并销毁旧对象):

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;
}

因此,编译器所做的这种细微区分可以显著提高性能。

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

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

  • C++通常签名的运算符重载指南
  • 运算符重载:一些 NIC 实现技巧的常见做法。