Operator overloading
 

Fundamentals:

There are many operators available that work on built-in types, like int and double.
Operator overloading means the process of creating new versions of these operators for use with user-defined types.

It is not as difficult as it sounds.  Some points to note:

Some rules and facts:

Friend:

Operator overloads can be written as member functions of a class or as friend functions.  Remember that the keyword friend is used to give something outside a class (like another class or a function) access to all of the members of the class.

A simple arithmetic operator overload:

To see how operator overloading works, consider the following example.

We should have no trouble conceptually with the normal uses of the arithmetic operators:

 int x = 3, y = 6, z; 
 float a = 3.4, b = 2.1, c; 
 z = x + y; 
 c = a / b; 

However, consider the idea of doing arithmetic on our Fraction objects.  How can we accomplish it?  Would the following make sense to the compiler?

 Fraction n1, n2, n3; 
 n3 = n1 + n2;        // is this legal? 

It should be clear that this would not make sense to the compiler.  How would the computer automatically know about getting the common denominator, for instance?  Fraction is a user-defined type -- the language designers didn't know we were going to build it!

So, how do we add Fraction objects?  One way is to write a function, let's call it Sum, that takes two Fraction objects as parameters, and returns a Fraction as a result:

 Fraction Sum(Fraction f1, Fraction f2); 

However, the + notation would certainly be more convenient!  We can write the same function (just like the one above) using an operator overload instead.

Format:  Since an operator is a function, we declare and define one just like we would a function.  A declaration of an operator overload requires a name (which is the operator symbol), a return type, and a parameter list.  In addition, we need the keyword operator.

 ReturnType operator OperatorName(ParameterList)

Example of the Fraction class operator overload declaration for +

 Fraction operator+(Fraction f1, Fraction f2); 

Notice that this function is not a member function of the Fraction class.  Instead, two Fraction objects are passed in as parameters.  If we want this function to have access to the private data, we can declare this function to be a friend of the Fraction class:

 friend Fraction operator+(Fraction f1, Fraction f2); 

Note that this is not something that should be done with all functions.  It is a generally accepted practice for operator overloads, since this function is intended for use with this class and it allows faster performance.

Here is how this function might be defined.  Notice that we must tell the computer how to DO the addition -- i.e. finding the common denominator and adding the adjusted numerators. (If we wrote the Sum function listed above, it would have a very similar definition.)

 Fraction operator+(Fraction f1, Fraction f2) 
 { 
   Fraction r;        // declare a Fraction to hold the result 

   // load result Fraction with sum of adjusted numerators 
   r.numerator = (f1.numerator*f2.denominator) 
                  + (f2.numerator*f1.denominator); 

   // load result with the common denominator 
   r.denominator = f1.denominator * f2.denominator; 

   return r;         // return the result Fraction 
 } 

Once this operator overload is defined, then the following is legitimate:

 Fraction n1, n2, n3; 
 n3 = n1 + n2;        // now it is legal! 

Click here to see the Fraction class with the + operator added in.

Operator overloads as member functions:

Some operator overloads can be defined as member functions or as outside (and possibly friend) functions.  Others must be one or the other.  If a binary operator is defined as an outside function, the function prototype will have two parameters.  If a binary operator is defined as a member function, the prototype will list one parameter (the second operand) - the first operand will be the object itself.

Example of the Fraction class + overload as a member function.

 Fraction Fraction::operator+(Fraction f2) 
 { 
   Fraction r;      // result 
   r.numerator = (numerator * f2.denominator) 
                  + (f2.numerator * denominator); 
   r.denominator = (denominator * f2.denominator); 
   return r; 
 } 

Notice that there is no dot-operator on one of the sets of member data variables.  These variables are part of the object we are in (as a member function).  The second object was passed in as a parameter.  In the following call, the + operator will run as a member function of object n1, and object n2 will be passed in as a parameter.

 Fraction n1, n2, n3; 
 n3 = n1 + n2; 

Click here to see the Fraction class with the + operator as a member function.

Other examples of operator overloads (prototypes only):

 // multiplication overload for Fractions 
 friend Fraction operator*(Fraction f1, Fraction f2); 

 // less-than comparison overload for Fractions 
 friend bool operator<(Fraction f1, Fraction f2); 

 // addition operator to add a Fraction and an integer 
 friend Fraction operator+(Fraction f, int n); 

 // same as above, but this one allows the int to come first in the call 
 friend Fraction operator+(int n, Fraction f); 


Overloading the insertion << and extraction >> operators:

As with other operators, the << and >> operators are defined for the basic types.  If you build your own class, don't expect << to automatically work with your new types of objects!   If you want it to work, you have to teach the computer how to do such output.  Consider the following:
 Fraction f; 
 cout << f;        // how would the machine know how to do this? 

We have no reason to expect the second line to work!  The insertion operator << is only pre-defined for built-in types.  The iostream.h library doesn't know about the Fraction type.

The << operator is a binary operator (2 parameters, left side and right side).  The first parameter is always an ostream object (we've mostly used cout, so far).  Because of this, it cannot be defined as a member function (it would have to be a member of the ostream class, which we cannot change).  The << and >> operators should always be defined as outside functions (usually friend functions).  The second parameter is whatever new type it is being overloaded to print:

 friend ostream& operator << (ostream& s, Fraction f); 

This declaration has all of the usual parts for defining a function.  The name is operator<< (the keyword operator and the operator symbol). The return type is ostream&. The parameters are (ostream& s, Fraction f). When defining overloads of << and >> , always pass the stream parameters by reference. A better way to write this operator is:

 friend ostream& operator << (ostream& s, const Fraction& f); 

Notice that the first one passes the Fraction by value (and makes a copy). The second passes by reference (avoiding the overhead of a copy). It is declared as a const because the Fraction does not need to change if we are just doing output.

Here is the corresponding prototype for extraction >>

 friend istream& operator >> (istream& s, Fraction& f);

Notice that the Fraction parameter for >> is also a reference parameter.  This is because we are getting input into the object, so we need to work on the original, not a copy.

Remember the Show() function of the Fraction class?

 void Fraction::Show() 
 { 
   cout << numerator << '/' << denominator; 
 } 

Here is how the << operator would be defined for Fraction.  Notice how similar it is to the Show() function.

 ostream& operator << (ostream& s, const Fraction& f) 
 { 
   s << f.numerator << '/' << f.denominator; 
   return s; 
 } 

Note the differences between this and the Show() function.


Once this is defined, we can use a Fraction object in a cout statement:

  Fraction f1; 

So now, instead of:

  cout << "Fraction f1 is "; 
  f1.Show(); 
  cout << '\n'; 

We can write:

  cout << "Fraction f1 is " << f1 << '\n';

Click here to see the Fraction class with the << overload used instead of Show().

Now, what would the definition of the >> overload look like for Fraction?  Try it!


Phonebook database example with << and >> overloads in the Entry class.