11.5.2. operator<< And operator>> With Inheritance

Review

operator<< and operator>> are frequently overloaded for new classes. On their own, these functions are pretty simple once you learn the basic pattern. But when used in an inheritance hierarchy, especially when chained together, some unusual syntax is required. Fortunately, there is also a pattern that we can learn that makes overloading these functions in the presence of inheritance fairly straightforward.

All three inserter operators are presented below follow the operator<< the pattern outlined previously. All three are friend functions, return an ostream reference, have an ostream reference for the first argument, and have a reference to the defining class as the second argument. But Actor and Star add something new, which is highlighted in the following figure.

Person
friend ostream& operator<<(ostream& out, Person& me)
{
    out << me.name << endl;
    return out;
}
A picture of a UML class diagram showing three classes related by inheritance.

Person
-------------------
-name : string
-------------------
+Person(n : string)
+operator<<(out : ostream&, me Person&) : ostream&

Actor
-------------------
-agent : string
-------------------
+Actor(n : string, a : string)
+operator<<(out : ostream&, me Actor&) : ostream&

Star
-------------------
-balance : double
-------------------
+Star(n : string, a : string, b : double)
+operator<<(out : ostream&, me : Star&) : ostream&
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;
}
Chaining inserter calls from subclasses to superclasses. The compiler always selects which overloaded function to call by matching the arguments in the function call with the parameters in the function's signature. The first parameter (i.e., the left-hand operand) is alway an ostream reference, so 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 two sperclasses, and the Star object can't access their members directly; it must use their public interfaces, operator<<. So, our goal is to create a chain of function calls:

The highlighted casting operations form expressions that convert a Star reference into an Actor reference, and an Actor reference into a Person reference. Following the cast, the expression matches the second or right-hand operand of the << operator (i.e., the expression matches the second argument in the superclass operator<< function). The sequence of chained function calls begins when the Star operator<< function is called. The numbers indicate the order in which action is performed:

  1. Star operator<< is called
  2. Star operator<< calls the Actor operator<<
  3. Actor operator<< calls the Person operator<<
  4. Person operator<< prints the Person's name and then returns
  5. Actor operator<< prints the Actor's agent's name and then returns
  6. Star operator<< prints the Star's bank balance and the returns