为什么链接需要运算符到 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&’
但是我无法理解理论上有什么问题。我认为会发生什么:
- 首先为对象
c
调用了 operator=。它通过引用接收对象 a
,将其值复制到 c 和 return 自身(对象 c
的)的匿名副本:调用复制 soncstructor。
- 然后为对象
b
调用 operator=。它需要右值引用,但我们只写了左值引用,所以出现错误。
我添加了 rval operator= 和复制构造函数,它接收左值引用并且一切正常,现在我不知道为什么(我应该编写接收 const Alpha& s
或 Alpha&& 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;
}
因此,编译器做出的这种细微差别可以显着提高性能。
顺便说一下,这不应与使用相同 &&
语法但在模板中的通用引用相混淆,后者让编译器选择最合适的右值或左值引用。
编辑:您可能对以下文章感兴趣:
- C++ operator overloading guideline 为通常的签名
- Operator overloading: common practice 一些不错的实现技巧。
这个赋值运算符的签名
Alpha operator=(Alpha& another)
在两个方面是不寻常的。首先是它 return 是分配给对象的副本。很少这样做。另一个是它接受一个非常量引用作为参数。
非 const 引用使其不接受临时对象作为参数(因为它们只会绑定到 const 左值引用)。
结合起来,这意味着从第一个 operator=
临时 returned 不能用作第二个 operator=
的参数。
您的选择是 return 一个参考,或者使参数 Alpha const&
。这两个选项可以单独使用,也可以组合使用。
正如您发现的,第三个选项是显式添加移动赋值运算符,使用 Alpha&&
专门接受临时对象。
虽然标准方法是声明复制赋值运算符
Alpha& operator=(Alpha const& other);
除非您有非常选择其他签名的具体原因。
应该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&’
但是我无法理解理论上有什么问题。我认为会发生什么:
- 首先为对象
c
调用了 operator=。它通过引用接收对象a
,将其值复制到 c 和 return 自身(对象c
的)的匿名副本:调用复制 soncstructor。 - 然后为对象
b
调用 operator=。它需要右值引用,但我们只写了左值引用,所以出现错误。
我添加了 rval operator= 和复制构造函数,它接收左值引用并且一切正常,现在我不知道为什么(我应该编写接收 const Alpha& s
或 Alpha&& 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;
}
因此,编译器做出的这种细微差别可以显着提高性能。
顺便说一下,这不应与使用相同 &&
语法但在模板中的通用引用相混淆,后者让编译器选择最合适的右值或左值引用。
编辑:您可能对以下文章感兴趣:
- C++ operator overloading guideline 为通常的签名
- Operator overloading: common practice 一些不错的实现技巧。
这个赋值运算符的签名
Alpha operator=(Alpha& another)
在两个方面是不寻常的。首先是它 return 是分配给对象的副本。很少这样做。另一个是它接受一个非常量引用作为参数。
非 const 引用使其不接受临时对象作为参数(因为它们只会绑定到 const 左值引用)。
结合起来,这意味着从第一个 operator=
临时 returned 不能用作第二个 operator=
的参数。
您的选择是 return 一个参考,或者使参数 Alpha const&
。这两个选项可以单独使用,也可以组合使用。
正如您发现的,第三个选项是显式添加移动赋值运算符,使用 Alpha&&
专门接受临时对象。
虽然标准方法是声明复制赋值运算符
Alpha& operator=(Alpha const& other);
除非您有非常选择其他签名的具体原因。