Continue Object Oriented Software Design
Overloading I/O operators using friend functions.
The only method we currently have for accessing and manipulating private class data members is through the class's member functions. There are times, however, when it is useful to provide such access to selected nonmember functions. The procedure for providing this external access is rather simple - the class maintains its own approved list of nonmember functions that are granted the same privileges as member functions. The nonmember functions on the list are called friend functions, and the list is referred to as a friend's list.
Any nonmember function
attempting access to an object's private data members if first checked against
the friends list: If the function is on the list, access is approved;
otherwise access is denied.
Four points to remember about friend functions:
Overloading The I/O Operators
Every C++ compiler provides a class library that includes a number of predefined classes. Three of these classes are named ostream, istream and iostream. The istream name is derived from instream, the ostream name from outstream, and the iostream name from input/output stream. In this context, a stream is simply a one-way path between a source and a destination down which a sequence of bytes can be sent.
The insertion, or "put to," operator << is both defined and overloaded in the ostream class to handle the output of built-in types, while the extraction, or "get from," operator >> is both defined and overloaded in the istream class to handle input of built-in types. The capabilities of both the ostream and istream classes are available to the iostream class (through the process of inheritance). Thus, we have access to the cin and cout streams and to the insertion and extraction operators through the iostream class that we have been including in all of our programs. This access permits us to create our own overloaded versions of the << and >> operator functions to handle user defined object types.
The process of making cin extractions and cout insertions available to a user-defined class consists of these steps:
Here is an example which overloads
the insertion and extraction operators to handle objects of type Date.
In reviewing the example, first notice that within the main() function a Date object is entered using cin and is output using cout. Now take a look at the class declaration for Date and notice that two friend functions have been included in the friend's list using these function prototype declarations:
friend ostream& operator<< (ostream&, const Dae&);
friend istream& operator>> (istream&, Date&);
The first declaration makes the overloaded insertion operator function << a friend of the Date class, while the second statement does the same for the overloaded extraction operator function >>. In the first declaration the << operator has been declared to return a reference to an ostream object and to have two formal parameters, a reference to an ostream and a reference to a Date class, which is a constant. In the second declaration the >> operator has been declared to return a reference to an istream object and to have two formal parameters, a reference to an istream object and a reference to a Date object. By simply changing the class name Date to the name of any other class and including these declarations within the class's declaration section, these two prototypes can be used in any user-defined class. Thus, the general syntax of these declarations, applicable to any class is:
friend ostream& operator<<(ostream&, const class-name&);
friend istream& operator>>(istream&, class-name&);
Consider now the implementations of these overloaded functions. Consider first the overloaded insertion operator function:
ostream& operator<<(ostream& out, const Date& adate)
{
out << adate.month << '/' << adate.day << '/' << adate.year;
return out;
}
Although the name of the reference to a Date object has been named adate, any user-selected name would do. Similarly, the argument named out, which is a reference to and ostream object, can be any user-selected name. Within the body of the function we insert the month, day, and year members of the Date object to the out object, which is then returned from the function. Also notice the notation used in inserting the month, day, and year to out namely:
adate.month
adate.day
adate.year
This notation follows the dot notation that includes both the object name and attribute name with the names separated by a period. This was the reason for making the overloaded operator function a friend of the Date class. By doing so, the overloaded insertion operator has direct access to a Date object's month, day, and year data members.
Now consider the implementations of the overloaded extraction operator function:
// overloaded extraction operator function
istream& operator >> (istream& in, Date& somedate)
{
in >> somedate.month; // accept the month part
in.ignore(1); // ignore 1 character, the / character
in >> somedate.day; // get the day part
in.ignore (1); // ignore 1 character, the / character
in >> somedate.year; // get the yea part
return in;
}
The header line for this function declares that it will return a reference to an istream object and has two reference parameters: a reference to an istream object and a reference to a Date object. The parameter names, in and somedate, can be replaced by any other user-selected names.
The body of the function first extracts a value for the month member of the Date, then it uses the ignore() member function of istream to ignore the next input character, which is usually a /. The value for the day member is then extracted, the next character is ignored, and finally the value of the year member is extracted. Thus, if the user typed in the date 1/15/02 or the date 1-15-02, the overloaded extractor function would extract 1, 15, 02 as the month, day, and year values, respectively.
A nonmember function may access a class's private data members if it is granted friend status by the class. This is accomplished by declaring the function as a friend within the class's declaration section. Thus, it is always the class that determines which nonmember functions are friends; a function can never confer friend status on itself.