In C, a->b
is precisely equivalent to (*a).b
. The "arrow" notation was introduced as a convenience; accessing a member of a struct via a pointer was fairly common and the arrow notation is easier to write/type, and generally considered more readable as well.
C++ adds another wrinkle as well though: operator->
can be overloaded for a struct
/class
. Although fairly unusual otherwise, doing so is common (nearly required) for smart pointer classes.
That's not really unusual in itself: C++ allows the vast majority of operators to be overloaded (although some almost never should be, such as operator&&
, operator||
and operator,
).
What is unusual is how an overloaded operator->
is interpreted. First, although a->b
looks like ->
is a binary operator, when you overload it in C++, it's treated as a unary operator, so the correct signature is T::operator()
, not T::operator(U)
or something on that order.
The result is interpreted somewhat unusually as well. Assuming foo
is an object of some type that overloads operator->
, foo->bar
is interpreted as meaning (f.operator->())->bar
. That, in turn, restricts the return type of an overloaded operator->
. Specifically, it must return either an instance of another class that also overloads operator->
(or a reference to such an object) or else it must return a pointer.
In the former case, a simple-looking foo->bar
could actually mean "chasing" through an entire (arbitrarily long) chain of instances of objects, each of which overloads operator->
, until one is finally reached that can refer to a member named bar
. For an (admittedly extreme) example, consider this:
#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;
}
Even though foo->bar=1;
looks like a simple assignment to a member via a pointer, this program actually produces the following output:
Used f::operator->
used g::operator->
used h::operator->
int_proxy::operator= called
Clearly, in this case foo->bar
is not (even close to) equivalent to a simple (*foo).bar
. As is obvious from the output, the compiler generates "hidden" code to walk through the whole series of overloaded ->
operators in various classes to get from foo
to (a pointer to) something that has a member named bar
(which in this case is also a type that overloads operator=
, so we can see output from the assignment as well).