To allow operator overloading by non-member functions, the rules used by the compiler involve two steps. If an expression of the form "x op y" is encountered, the compiler will check:
When operator overloading is achieved using non-member function there are two cases to be considered:
Array a; // give values to a cout << "[ "; for(int i = 0; i< 19; i++) cout << a[i] << ", "; cout << a[19] << " ]";which prints our a comma separated list of numbers enclosed in brackets. This is tedious code to write each time and can be placed in the Array class itself as a "print" method as follows:
class Array private: int array[20]; public: Array(); int& operator[](int i); // subscript operator Array& operator+(Array& other); // addition operator Array& operator-(Array& other); // subtraction operator void Print(); ~Array(); };where the Print method is implemented as:
void Array::Print() { cout << "[ "; for(int i = 0; i < 19; i++) cout << array[i] << ", "; cout << array[19] << " ]"; }This approach to displaying the array is can be improved upon by noting that the stream I/O operators ( "<<", ">>" ) are operators that can be overloaded. If such an overloading could be accomplished then it would be possible to write:
Array a; // give values to a cout << a;
Overloading the stream I/O operators presents an apparent problem because it seems to require that the operator overloading must take place in the library class that implements stream I/O. Changing the library classes may be impossible (because the source code is not available), or at least troublesome (due to the need to understand the classes that implement the stream I/O functionality.
C++ allows operators to be overloaded by functions that are not members of a class. To provide the overloading of the stream I/O operators the following function could be defined:
ostream& operator<< (ostream& os , Array& a) { os << "[ "; for(int i = 0; i < 19; i++) os << a[i] << ", "; os << a[19] << " ]"; }The class "ostream" used in this example is the actual type of the predefined library variable "cout" (similarly "cin" is implemented by the class "istream"). Upon examining the statement:
cout << a;where "a" is an object of the class Array, the compiler will check if there is an overloaded operator of the form "operator<<(Array)" in the ostream class (which it will not find) and then will check if there is a non-member overloaded operator of the form "operator<<(ostream&, Array)" (which it will find).
Since the overloaded stream output operator is defined in terms of an "ostream" object, it applies not only to "cout" but to other objects that inherit from the ostream class. The "ofstream" class, for file output, inherits from the ostream class. Thus, the overloading above allows:
ofstream outFile("array.data"); Array a; // give values to a outFile << a;Similarly, writing to "string streams" is also possible.
An example of where special access is required is when efficient binary level I/O is added to the Array class given above. What is desired is a way to write the contents of the Array into a file in binary form, not in ASCII (text form). This form of I/O is faster and is more compact. It is faster because one write operation is done to write the entire array. It is more compact because an integer, say 5,210,500, is stored in 4 bytes (32 bits) in the file in binary form but requires at least 8 byes in ASCII. However, to perform the binary level I/O operation it is necessary to know the address of the array to be written. However, it is undesireable to add a method in the Array class to return the base address of its encapsulated array as this would allow any code to manipulate the encapsulated array. A better design in this situation is to allow the non-member function that implements the stream I/O to have special access. This allows the overloading function, and only the overloading function, to have access to the base address of the encapsulated array.
The binary form of stream I/O should look and behave as the other forms of stream I/O overloading. Thus, binary form of the stream I/O should be usable as follows:
ofstream binaryFile("array.bin"); Array a; //... give values to a binaryFile << a; // output array in binary form binaryFile.close();To make this work, an overloading of the stream I/O operators can be written that operate on files (ofstream, ifstream). For output, the following non-member functions is what is needed:
ofstream& operator<< (ofstream& ofs, Array& a) { ofs.write((char*)(a.array), 20*sizeof(int)); return ofs; };As can be seen, this non-member function requires access to the private data of the Array class. To allow this access to be granted it is necessary to give the non-member function special permission. Special access is granted by a class declaring another function (or class) as its "friend". For the non-member function considered here, the Array class would be modified as follows:
class Array { private: int array[20]; public: ... friend ofstream& operator<< (ofstream& ofs, Array& a); ... };The "friend" declaration grants the access to the private data that is required.
Notice also that two overloadings of the stream I/O operators are provided - one for ostream& objects (like cout) and one for ofstream& objects (like the binaryFile above). These overloading perform ASCII (text) output for streams that are typically viewed by users and binary I/O on files.
Last Updated: November 17, 1995 / kafura@cs.vt.edu