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.

  Access Provided to Nonmember Functions     

 

 

 

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.

Example

Four points to remember about friend functions:                                                                

  1. Since friends are not class members, they are unaffected by the access section in which they are declared-they may be declared anywhere within the declaration section.
  2. The keyword friend is used only within the class declaration and not in the actual function definition.
  3. Since a friend function is intended to have access to an object's private data members, at least one of the friend's arguments should be a reference to an object of the class that has made it a friend.
  4. It is the class that grants friend status to a function and not the other way around. The function can never confer friend status on itself, because to do so would violate the concepts of data hiding and access provided by a class.

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:

  1. Make each overloaded operator function a friend of the user-defined class. This ensures that these overloaded functions will have access to a class's private data members.
  2. Construct an overloaded version for each operator function that is appropriate to the user-defined class.

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.