<<
)Rather than writing a print()
member function for a class, it can be convenient to overload the stream insertion operator to work with our new data type.
Because the left operand of the stream insertion operator is an object of class ostream
(like cout
) and not an object of our new class, we must overload the operator as a standalone function.
Here is the skeleton code for overloading the stream insertion operator:
ostream& operator<<(ostream& os, const ClassName& obj) { // Print the data members of obj using os like you would using cout // Return the output stream object so the operator may be cascaded correctly return os; }
Example 1: The <<
operator overloaded as a standalone friend
function of the Rational
class
To designate our overloaded operator function as a friend
of the Rational
class, we need to include the function's prototype, preceded by the keyword friend
, anywhere in the declaration of the Rational
class:
class Rational
{
friend ostream& operator<<(ostream&, const Rational&);
private:
int numerator,
denominator;
public:
.
.
.
};
The function definition can then be coded like so:
ostream& operator<<(ostream& os, const Rational& obj)
{
os << obj.numerator << '/' << obj.denominator;
return os;
}
Once we've overloaded this operator, we can use it to print an object of the Rational
class using cout
instead of calling a print()
member function:
Rational r1(3, 5); // Create r1 and set it = 3/5
cout << r1; // Generates the function call operator<<(cout, r1) and prints 3/5
There's one problem with the code outlined above: it will break the setw
manipulator, because setw
only affects the next item inserted into the output stream, and the code above inserts multiple items into the output stream. So, for example, if you try to write something like this, you won't get the results that you might expect:
Rational r1(5, 10);
cout << left << '*' << setw(6) << r1 << '*' << endl;
Instead of the manipulator being applied to the entirety of the value of r1
, the numerator will be inserted into the output stream with a width of 6, and then the '/'
and denominator will be inserted, giving the following output:
*5 /10*
The result is that the total width of what was printed was 9 characters, not the requested 6.
A solution to this problem is to insert our multiple items into an output stringstream, and then convert the output stringstream into a string and insert that string into the output stream.
ostream& operator<<(ostream& os, const Rational& obj)
{
ostringstream output_text; // Declare an output stringstream
// Insert our output into the output stringstream object
output_text << obj.numerator << '/' << obj.denominator;
// Convert the output stringstream object to a string and insert it into
// the output stream
os << output_text.str();
return os;
}
Now the cout statement shown above will work as expected. Since we're only inserting a single string into the output stream, the setw
manipulator is correctly applied to the single string, printing it 6 characters wide:
*5/10 *
The return value from the overloaded operator function is required in order to cascade the operator correctly. Consider the following typical use of the cascaded stream insertion operator. How does the compiler evaluate this statement?
cout << "The value of r1 is " << r1 << endl;
The <<
operator is evaluated from left to right, so this part of the expression is evaluated first:
cout << "The value of r1 is "
The text string is printed and then a reference to the ostream
object cout
is returned. The return value effectively replaces the sub-expression that has been evaluated, and as a result, the remainder of the expression becomes:
cout << r1 << endl;
The overloaded stream insertion operator function then generates a function call to print r1
and (if written correctly) returns a reference to the ostream
object that was passed into the function (cout
, in this case). The return value replaces the function call and the remaining part of the expression then becomes:
cout << endl;
If your overloaded stream insertion operator function doesn't return a reference to the ostream
object passed in as the first argument, the last step would not work correctly - you'd get a syntax error on the statement instead since the left operand for the next call to the operator function would be the wrong data type.
Writing an overloaded operator function to use the <<
operator to write to a file stream is trivial - simply replace the data type ostream
with the data type ofstream
in the function prototype and definition. Of course, you'll have to open the file stream object for output before you can write to it.