operator<<
and operator>>
(the inserter and extractor operators, respectively) are frequently overloaded for new classes. The implementing functions follow a strict pattern, so overloading them is relatively easy. However, some unusual syntax is required when implementing them in classes forming an inheritance hierarchy, especially when chaining the calls together. Fortunately, we can extend the patterns to cover operations chained through inheritance.
Individually, each I/O operator follows the pattern outlined previously. Each is a friend
function. The inserter always returns an ostream reference and has an ostream reference as its first parameter; the extractor always returns an istream reference and has an istream reference as its first parameter. Each operator function has a reference to the befriending class as its second parameter. However, the subclasses, Actor and Star, add expressions calling their immediate superclass' I/o operator functions. Forming that expression is the focus of this section.
Person |
friend ostream& operator<<(ostream& out, Person& me) { out << me.name << endl; return out; } |
|
---|---|---|
Actor |
friend ostream& operator<<(ostream& out, Actor& me) { out << (Person &)me << " " << me.agent << endl; return out; } |
|
Star |
friend ostream& operator<<(ostream& out, Star& me) { out << (Actor &)me << " " << me.balance << endl; return out; } |
ostream
reference. Therefore, the compiler matches a function call to the corresponding function based on the second parameter (i.e., the right-hand operand).
A Star is an Actor and an Actor is a Person. So, a Star object has a balance and, by inheritance, an agent and a name. If a program prints a Star object, it should print all three data members. However, the inherited members are private
in the superclasses, and the Star object can't access their members directly; it must use their public interfaces, specifically operator<<. Calling the superclass inserter requires converting a subclass object to a superclass. For example, an Actor is a Person by inheritance, so converting an Actor to a Person makes sense.
Class | Function Signature | Casting To Superclass |
---|---|---|
Person | friend ostream& operator<<(ostream& out, Person& me) | N/A |
Actor | friend ostream& operator<<(ostream& out, Actor& me) | (Person &)me |
Person | friend ostream& operator<<(ostream& out, Star& me) | (Actor &)me |
The casting operations form expressions converting a Star reference to an Actor reference and an Actor reference to a Person reference, but they don't alter the values saved in me. The expressions have statement scope, and the program discards their effect when the statement ends. Following the cast, the expressions match the second or right-hand operand of the << operator (i.e., the expression matches the second parameter in the superclass operator<< function).
Person |
friend istream& operator>>(istream& in, Person& me) { getline(in, me.name); return in; } |
|
---|---|---|
Actor |
friend istream& operator>>(istream& in, Actor& me) { in >> (Person &)me; getline(in, me.agent); return in; } |
|
Star |
friend istream& operator>>(istream& in, Star& me) { in >> (Actor &)me; in >> me.balance; return in; } |