总结:
尝试a+(b+c) 并观察您的会员版本失败。
C++ 具有命名值(又名左值,因为它们是在 = 赋值的左侧有意义的值)和临时值(又名右值,在 = 赋值的右侧进行) )。 (本段包含简化)。
lvalues 将绑定到非 const 引用,而 rvalues 将只绑定到 const 引用。所以Int& 不会绑定到临时的,而Int const& 或const Int&(同样的事情)会。
您得到的错误是 a+b+c 按 C++ 中的操作顺序被解析为 (a+b)+c。 a+b 的结果是一个临时的(右值)。所以它不会绑定到Int&。
像Int operator+(Int& rhs) 这样的方法将*this 作为+ 的左侧参数,rhs 是右侧参数。所以(a+b)+c、a+b 是*this 和c 是rhs。成员operator+ 是临时工,所以一切都很好。
像Int operator+(Int&, Int const&) 这样的朋友——您会注意到Int& 在左侧。 (a+b)+c中的左边是加+c时的临时。所以它失败,因为a=b 不会绑定到Int&
最后,成员绑定到临时对象的方式存在一些怪癖。因为成员在右值/左值引用之前添加到 C++ 20 多年,所以它们与临时对象的绑定比您预期的要宽松得多(默认情况下)。
更详细:
您的operator+ 成员在右侧采用非常量引用。您的朋友在左侧采用非常量引用。
C++ 中的a+b+c 被解析为(a+b)+c。在任何情况下,+ 的右侧都有一个命名值——一个左值——并且命名的可变值可以绑定到非常量引用。
但是,+c 在左侧有一个临时值 - 一个右值 - 并且临时值不能绑定到非 const 左值引用。
最后一个怪癖与这样的成员有关:
Int Int::operator + (Int & v)
{
return Int(a + v.a);
}
可以在右值(临时)上调用,即使它是非常量的。对象的 l/r 值限定是在初始成员语法之后添加的。
您可以通过多种方式声明“该方法适用于哪种对象,常量或非常量,临时或非临时对象”。他们的签名看起来像:
Int operator + (Int const& v);
Int operator + (Int const& v) const;
Int operator + (Int const& v) &;
Int operator + (Int const& v) const&;
Int operator + (Int const& v) &&;
Int operator + (Int const& v) const&&;
如果您不包含尾随 & 或 &&,您的方法将绑定到 右值(临时)和左值(具有标识的命名值)。这是因为 l/r 值引用是在方法添加到 C++ 后 20 多年才添加的,因此 default 方法声明适用于两者(出于向后兼容性的原因)。
friend Int operator + (Int &, const Int &);
对应
Int operator + (const Int &) &;
作为一种方法,它会失败并显示与朋友基本相同的错误消息。
你也可以诱导
friend Int operator + (Int &, Int const&);
通过键入来工作的方法:
a+(b+c)
因为现在+c 的左侧是临时的,并且拒绝绑定到Int&。
这也会使
Int operator+(Int&)
版本失败,因为现在a+的右边是(b+c),这是一个临时的,不会绑定到Int&。
一般建议:
在为类处理运算符时,请执行此操作。
Int operator+=(Int const& rhs)&;
这是 increment by 运算符。它应该是一个成员,并且最后应该有 & 限定符——增加一个临时值是没有意义的。
然后写:
friend Int operator+(Int lhs, Int const& rhs) {
lhs += rhs;
return lhs;
}
这里我们使用增量运算符来实现+。如果我们的类有高效的移动构造函数(应该如此),这也将非常接近最优。
我们采用值的左侧参数。然后我们增加它。然后我们返回它(这会隐式移动)。
当你输入时:
a+b+c
这变成了
(a+b)+c
我们复制a 并得到:
copy of a += b
然后我们将 a 的副本移动到结果中。然后将其省略(零成本移动)到+c 的左侧参数中。
然后我们将其递增,并将其移动到结果中。
因此,消除移动(即使在最复杂的对象上也应该接近零成本),我们得到:
Int temporary = a;
temporary += b;
temporary += c;
return temporary;
a+b+c 的结果。
这种模式适用于简单的东西,比如假整数,更复杂的东西,比如字符串,以及无数其他情况。它为您提供+= 和+ 的编码工作量与通常编写+ 所做的工作量大致相同。