![]() |
Robert Allan SchwartzRead my professional biography. Contact me. Go to my home page. Read my publications. Read my professional resume. See some student reviews of my teaching. |
Copyright © 1998 by Robert Allan Schwartz
[published in C++ Report, April 1998, page 14.]
What's the best way for class my_class to support operator<<()?
It can't be a member of class ostream (unless you want to modify class ostream, which I don't). It can't be a member of class my_class, because the instance of class my_class is the right operand, not the left operand. It must be a non-member.
As a non-member, it has no access to class my_class's private data members. How can it get access? One way is for it to use public member get functions. However, not all private data members have (or want to have) public member get functions. Another way is for class my_class to grant friendship to the non-member operator<<() function. Unfortunately, friendship grants access to all data members, not just some, and it grants read and write access, not just read access.
But there's a more serious problem with friendship. Friendship isn't inherited, and it isn't virtual.
Begin Code Listing 1
class base { public: base(int new_b) : b(new_b) { } private: int b; friend ostream & operator<<(ostream & s, const base & x); }; ostream & operator<<(ostream & s, const base & x) { // output this class's data members: s << x.b << endl; return s; } class derived : public base { public: derived(int new_b, int new_d) : base(new_b), d(new_d) { } private: int d; friend ostream & operator<<(ostream & s, const derived & x); }; ostream & operator<<(ostream & s, const derived & x) { // output the base class's data members: operator<<(s, (const base &) x); // output this class's data members: s << x.d << endl; return s; } void main(void) { base * bp = new derived(1, 2); cout << *bp; }
Output:
1
End Code Listing 1
"cout << *bp" is equivalent to "operator<<(cout, *bp)", which is a non-member, which means it is non-virtual, which means its static type ("*bp" has static type base) is used to match operator<<(ostream & s, const base & x), rather than its dynamic type (*bp has dynamic type derived) which would match operator<<(ostream & s, const derived & x).
In short, it doesn't give the output we want.
Let's return to the crux of the problem. We want non-member operator<<() to have access to class my_class's private data members. Friendship wasn't enough to solve the problem. What would be enough?
There needs to be a virtual function in there somewhere, since we want to use the dynamic type of "*bp", not the static type.
The solution is an idiom I call "Acquaintance": a non-member function invokes a virtual member function. The member function already has access to all the private data members, so there is no need to grant friendship, and it is virtual, so we use the dynamic type instead of the static type.
Begin Code Listing 2
class base { public: base(int new_b) : b(new_b) { } virtual void output(ostream & s) const; private: int b; }; void base::output(ostream & s) const { // output this class's data members: s << b << endl; } ostream & operator<<(ostream & s, const base & x) { x.output(s); return s; } class derived : public base { public: derived(int new_b, int new_d) : base(new_b), d(new_d) { } virtual void output(ostream & s) const; private: int d; }; void derived::output(ostream & s) const { // output the base class's data members: base::output(s); // output this class's data members: s << d << endl; } void main(void) { base * bp = new derived(1, 2); cout << *bp; }
Output:
1
2
End Code Listing 2
What changes did we make?
class base and class derived no longer grant friendship to operator<<(). class base and class derived each acquire a virtual member function, output(). class derived no longer needs its own overload of operator<<()!
"cout << *bp" is equivalent to "operator<<(cout, *bp)", which is a non-member, which means it is non-virtual, which means its static type ("*bp" has static type base) is used to match operator<<(ostream & s, const base & x), which invokes x.output(s), which is virtual, and invokes derived::output(), which invokes base::output().
In short, it gives the output we want.
The moral of the story? When friendship isn't enough, try an Acquaintance!