本节书摘来自异步社区出版社《C++覆辙录》一书中的第2章,第2.11节,作者: 【美】Stephen C. Dewhurst(史蒂芬 C. 杜赫斯特),更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.11:运算符函数名字查找的反常行为
重载的运算符真的只不过就是可以用中序语法调用的地地道道的成员函数或非成员函数罢了。它们是“语法糖”:
class String{
public:
String &operator =(const String&);
friend String operator +(const String&, const String&);
String operator–();
operator const char*() const;
// ...
};
String a, b, c;
// ...
a = b;
a.operator =(b); // 和上一个语句意义相同
a + b;
operator + (a, b); // 和上一个语句意义相同
a = -b;
a.operator =(b.operator-()); // 和上一个语句意义相同
const char *cp = a;
cp = a.operator const char*(); // 和上一个语句意义相同```
如果要评选“最佳清晰奖”,那么中序记法必可荣膺。典型情况下,我们要使用一个被重载的运算符时都是用中序记法的(即“左手边操作数运算符右手边操作数”的写法)。毕竟我们之所以要重载运算符最原始的出发点不就是这个么?
一般地,当我们不用中序记法时,函数调用语法比对应的中序记法更清晰。一个教科书般的例子就是基类的复制赋值运算符在派生类的复制赋值运算符实现中被调用的场合:
class A : {
protected:
A &operator =(const A &);
//…
};
class B : public A {
public:
B &operator =(const B&);
//…
};
B &B::operator =(const B&b){
if (&b != this){
A::operator =(b); // 好过"(static_castconst>(this))=b"
// 为B的其他局部变量赋值
}
return *this; ⑾
}`
⑾译者注:返回*this
是一个习惯用法,支持连续赋值。
还有一些场合我们使用函数调用语法而不用中序记法——尽管中序记法在这些场合的使用完全正确合理——中序记法在这些场合显得太怪异丑陋,会让一个维护工程师花几分钟才能回过神来:
value_type *Iter::operator ->() const
{return &operator*();} // 好过"&*(*this)"```
还有一些让人左右为难的情况,不管中不中序,写出来的东西都挺难看的:
bool operator !=(const Iter &that) const
{return !(*this == that);} // 或者"!operator==(that)"`
无论如何请注意,使用中序语法时的名字查找序列和使用函数调用语法时不同,这会带来出人意料的结果:
class X{
public:
X &operator %( const X&) const;
void f();
// ...
};
X &operator %(const X&, int);
void X::f(){
X& anX = *this;
anX % 12; // 没问题,调用非成员函数
operator %(anX, 12); // 错误!
}```
当我们使用函数调用语法时,名字查找序列遵从标准形式。在成员函数X::f的情况下,编译器首先在class X里找一个名字叫“`operator %`”的函数。只要找到了,它就不会在更外层的辖域里继续找其他同名的函数了。
不幸的是,我们企图向一个二元运算符传递3个实参。因为成员函数`operator %`有一个隐式的实参`this`,我们显式向它传递的2个实参会让编译器误以为我们想要把一个二元运算符以不正确的三元形式调用。一个正确的调用或者显式地识别出非成员版本的`operator %`(`::operator %(anX, 12)`),或者向成员函数`operator %`传递正确数量的实参(`operator %(anX)`)。