在 C 中,a->b
精确地等价于(*a).b
。为方便起见,引入了“箭头”符号;通过指针访问结构的成员是相当普遍的,箭头符号更容易编写/键入,通常也被认为更具可读性。
不过,C++ 也增加了另一个问题:operator->
可以为struct
/重载class
。尽管在其他方面相当不寻常,但对于智能指针类来说这样做很常见(几乎是必需的)。
这本身并不罕见:C++ 允许重载绝大多数运算符(尽管有些运算符几乎不应该重载,例如operator&&
,operator||
和operator,
)。
不寻常的是如何operator->
解释重载。首先,虽然a->b
看起来像是->
一个二元运算符,但当您在 C++ 中重载它时,它被视为一元运算符,因此正确的签名是T::operator()
, notT::operator(U)
或按该顺序排列的东西。
结果的解释也有些不寻常。假设foo
是某种类型的重载对象operator->
,foo->bar
被解释为意义(f.operator->())->bar
。这反过来又限制了重载的返回类型operator->
。具体来说,它必须返回另一个也重载的类的实例operator->
(或对此类对象的引用),否则它必须返回一个指针。
在前一种情况下,看起来简单foo->bar
实际上可能意味着“追逐”整个(任意长)对象实例链,每个实例都重载operator->
,直到最终到达可以引用名为 的成员bar
。对于一个(公认的极端)示例,请考虑以下内容:
#include <iostream>
class int_proxy {
int val;
public:
int_proxy(): val(0) {}
int_proxy &operator=(int n) {
std::cout<<"int_proxy::operator= called\n";
val=n;
return *this;
}
};
struct fubar {
int_proxy bar;
} instance;
struct h {
fubar *operator->() {
std::cout<<"used h::operator->\n";
return &instance;
}
};
struct g {
h operator->() {
std::cout<<"used g::operator->\n";
return h();
}
};
struct f {
g operator->() {
std::cout<<"Used f::operator->\n";
return g();
}
};
int main() {
f foo;
foo->bar=1;
}
尽管foo->bar=1;
看起来像是通过指针对成员的简单赋值,但该程序实际上会产生以下输出:
Used f::operator->
used g::operator->
used h::operator->
int_proxy::operator= called
显然,在这种情况下foo->bar
不(甚至接近)等价于简单的(*foo).bar
. 从输出中可以明显看出,编译器生成“隐藏”代码以遍历->
各种类中的整个重载运算符系列,以获取foo
(指向)具有名为的成员bar
(在本例中也是一个类型重载operator=
,所以我们也可以看到赋值的输出)。