Contents |
The goal is to make a class outputtable to an ostream, like this:
A a; cout << "The value of A is: " << a << endl;
This can be achieved by overloading operator << to handle the A class. The operator will probably need access to private members of the class, so we declare it a friend of the class:
class A {
friend ostream & operator << (ostream & out, const A &);
int value_;
};
ostream & operator << (ostream & out, const A & a) {
return out << a.value_;
}
The operator and friend must be declared for each class, which ends up being pretty error-prone.
The operator can be used like this:
out << a << " " << b;
which is equivalent to:
(((out << a) << " " ) << b);
To clarify, we can replace operator<< with a print-method with the same type:
ostream & print(ostream & out, const A & a);
Which could be used like this:
print( print( print(out, a), " "), b);
Defining operator<< for each class has a problem:
Printing an instance of a class will always use the static type of the instance, not the dynamic type. Thus you cannot print objects polymorphically.
Take a look at the following example:
class A { };
ostream & operator <<(ostream & out, const A & a) {
out << "I am an A"; return out;
}
class B : public A { };
ostream & operator <<(ostream & out, const B & b) {
out << "I am a B"; return out;
}
B b;
A & a = b;
cout << a << endl;
The result is:
I am an A
even though a is an instance of class B.
To use polymorphism, we have to make operator<< a method instead. Here is a naive example:
class A {
public:
virtual ostream & print( ostream & out ) const;
};
A a, b;
b.print( a.print( out ));
Using this approach is unbearably ugly and doesn't work with built-in types.
The solution for this problem is a combination of a method and an operator<<.
class A {
public:
virtual void print(ostream & out) const {
out << "I am an A";
}
};
inline ostream & operator << ( ostream & out, const A & a ) {
a.print(out); return out;
}
All printable base classes should have an operator << that calls print on the second argument. Thus, we get the nice syntax, polymorphism, and as an added bonus, we can completely drop the friend declaration, because print is now a method.
Polymorphism works, as you can see from this example:
class A {
public:
virtual void print(ostream & out) const {
out << "I am an A";
}
};
ostream & operator <<(ostream & out, const A & a) {
a.print(out); return out;
}
class B : public A {
public:
virtual void print(ostream & out) const {
out << "I am a B";
}
};
B b;
A & a = b;
cout << a << endl;
Which outputs:
I am a B
The ultimate solution reduces all the operator<< declarations to a single declaration. This is simply accomplished by declaring a Printable interface. To make a new class ostream outputtable, you only need to implement a print method. Here's the interface:
namespace N {
class Printable { // interface class
public:
virtual ~Printable() {}
virtual void print(ostream & out) const = 0;
};
inline ostream & operator << ( ostream & out, const Printable & p ) {
p.print(out); return out;
}
}
Declaring operator<< inside a namespace is important if you use other namespaces. It allows us to easily access it inside another namespace by 'using namespace N'.
Here's an example implementation of the Printable interface:
class A : Printable {
public:
virtual void print(ostream & out) const { out << "I am an A"; }
};
And an example of outputting A inside another namespace:
namespace M {
using namespace N; // get the operator<<
A a;
cout << a << endl;
}