Operator Overloading- II


The following topics are covered in this section:


Binary Operator Overloading

In the case of a unary operator we generally use no arguments. In the case of a binary operator we need to use one argument.

// Overloading the binary + operator
#include <iostream.h>
class time
{private:
    int hours,minutes;
public:
time( )
{
        hours=0;
        minutes=0;
}
time(int x,int y)
{
        hours=x;
        minutes=y;
}
void display( )
{
        cout<<endl<<hours<<" hours and "<<minutes<<" minutes.";
}
time operator + (time);
};

time time::operator + (time z)
{int h = hours + z.hours;
int m = minutes + z.minutes;
while (m>=60)
{
    m = m-60;
    h = h+1;
}
return time(h,m);
}

int main( )
{time t1(2,40);
time t2(3,30);
time t3;
t3 = t1+t2;
t3.display( );
return 0;
}

Let's take a closer look at the program.
The values for hours and minutes of t1 and t2 objects are initialized using the constructor function.
The compiler then reads: t3 = t1 + t2;

Remember: You cannot simply add two objects and assume that the compiler will add up the corresponding member data. Objects are user-defined data types and the operators will operate only on in-built data types.

Thus to operate on user-defined data types, we overload the operator and tell the compiler as to what it should do. On seeing the two operands, the compiler knows that + is overloaded and so it goes to the definition of the operator function. But we have a problem.

+ works on two operands, t1 and t2. It would appear that we need to pass two values to the operator function so that it can add these two values. But the operator function has only one parameter. What does this mean? It means that only one of the values are passed (i.e. only one object is passed). So, out of the two (t1 and t2) how do we know which one is passed? And what about the other object?

The operand to the right of the operator is passed as an argument. Hence the values of hours and minutes of t2 are passed to the operator function. Let's take a look at the first two lines within the operator function.
    int h = hours + z.hours;
    int m = minutes + z.minutes;

First of all, you know that t2 is passed as an argument. Hence, z actually represents t2 in our case.

In the expression:

int h = hours + z.hours;

what does the hours stand for?
This hours is the value of t1's hours. t1 is on the left of the operator and it is not passed as argument. Hence hours and minutes refer to the member data of t1.

In technical terms: When an overloaded operator is invoked, the object on the left of the operator is the object of which the operator is a member and the object on the right must be provided as argument to the operator. Actually the object on the left of the operand is passed to the operator function (remember the ‘this’ pointer which is always present in member functions; we don’t specify it explicitly, it is like a hidden argument). The ‘this’ pointer is the reason that you don’t need to pass any arguments while overloading unary operators.


Is there anything that cannot be overloaded?

Well, almost all operators can be overloaded in C++. You can overload even the [], the comma operator, new, delete etc. But the following operators cannot be overloaded:

Beware: If an operator is a binary operator, then you cannot overload it or convert it into a unary operator.


Overloading using Friend Functions

So far we have overloaded operators using member functions. We can also overload operators using the friend function. Friend functions are not members of the class and they do not have the ‘this’ pointer. Hence when you overload a unary operator you have to pass one argument. When you overload a binary operator you have to pass two arguments. Let’s see that same example that we saw earlier for overloading a binary operator.

class time
{private:
int hours,minutes;
public:
time( )
{
hours=0;
minutes=0;
}
time(int x,int y)
{
hours=x;
minutes=y;
}
void display( )
{
cout<<endl<<hours<<" hours and "<<minutes<<" minutes.";
}
friend time operator + (time, time);
};

time operator + (time y, time z)
{
    int h = y.hours + z.hours;
    int m = y.minutes + z.minutes;
    while (m>=60)
    {
        m = m-60;
        h = h+1;
    }
    return time(h,m);
}

int main( )
{
time t1(2,40);
time t2(3,30);
time t3;
t3 = t1+t2;
t3.display( );
return 0;
}

The output will be:

6 hours and 10 minutes

Just one point to note here: the operand on the left will be passed as the first argument while the operand on the right will be passed as the second argument.


Overloading a Unary Operator using Friend Function

When you overload a unary operator using a friend function you would have to pass one argument to the friend function.

Beware: When you use friend functions, they will not have the ‘this’ pointer. If you attempt to modify some value of an object passed as argument, then the friend function actually only operates on a copy (it does not act on the original object). This is because it is passed by value (and not as reference).

Thus you would actually have to work on a copy of the object, and then return a newly initialized object having the modified values (we did the same process in using friend function for binary operator overloading). To work directly on the original object, you can make use of reference parameters in the operator overloaded friend function.

//Overloading a unary operator using friend function

#include <iostream.h>
class timer
{

private :
int countdown;
public :
timer ( ) : countdown (100)
{ }
timer (int t) : countdown (t)
{ }
int display ( )
{
return countdown;
}
friend timer operator -- (timer &x);
};

timer operator --(timer &x)
{
x.countdown=x.countdown-1;
return x;
}

int main ( )
{ timer c1, c2;
cout<<"\nInitial c1 value : "<<c1.display( );
cout<<"\nInitial c2 value : "<<c2.display( );
--c2;
cout<<"\n\nFinal c1 value : "<<c1.display( );
cout<<"\nFinal c2 value : "<<c2.display( );
return 0;
}

The output will be:

Initial c1 value : 100
Initial c2 value : 100
Final c1 value : 100
Final c2 value : 99

The friend function header specifies that we want it to use reference parameters:

timer operator --(timer &x)

All the operations performed in this function are directly on the original object. Thus we can use the return statement:

return x;

instead of initializing a new object with the same member data values.


Overloading the Assignment Operator (=) and returning reference

There are some other aspects of operator overloading that will be illustrated in this section by overloading the assignment operator. The assignment operator has to set the left operand value to that of the right operand. Thus, when we equate objects we will want the same process to occur. The following program would seem quite adequate for the purpose:

class timer
{

private :
int countdown;
public :
timer ( ) : countdown (100)
{ }
timer (int t) : countdown (t)
{ }
int display ( )
{
return countdown;
}
void operator =(timer &x)
{
countdown=x.countdown;
}
};
int main ( )
{ timer c1, c2(98),c3(94);
cout<<"\nInitial c1 value : "<<c1.display( );
cout<<"\nInitial c2 value : "<<c2.display( );
cout<<"\nInitial c3 value : "<<c3.display( );
c1=c2;
cout<<"\n\nFinal c1 value : "<<c1.display( );
cout<<"\nFinal c2 value : "<<c2.display( );
cout<<"\nFinal c3 value : "<<c3.display( );
return 0;
}

The output will be:

Initial c1 value : 100
Initial c2 value : 98
Initial c3 value : 94
Final c1 value : 98
Final c2 value : 98
Final c3 value : 94

Actually everything in the above program seems to be perfect. We are making use of the pass by reference method. Instead of:

void operator =(timer &x)

you could write:

void operator =(timer x)

The program will work but a temporary object will be created (i.e. the function will work on a copy of the original object). When the function completes executing, the temporary object will be destroyed.

So, can you think of any flaw with the above program? What will happen if we write:

c1 = c2 = c3;

Try it and you would get an error. The problem is that we have overloaded = in such a way that it will not return anything (it returns void). The compiler will first evaluate:

c2 = c3;

Hence c2 will get the values of c3. But this evaluation does not return anything and the next step for the compiler will be:

c1 = void;

and this is not possible.

Thus you have to overload the assignment operator such that it will return something (it should return an object of course).

class timer
{

private :
int countdown;
public :
timer ( ) : countdown (100)
{ }
timer (int t) : countdown (t)
{ }
int display ( )
{
return countdown;
}
timer& operator =(timer &x)
{
countdown=x.countdown;
return *this;
}
};
int main ( )
{ timer c1, c2(98),c3(94);
cout<<"\nInitial c1 value : "<<c1.display( );
cout<<"\nInitial c2 value : "<<c2.display( );
cout<<"\nInitial c3 value : "<<c3.display( );
c1=c2=c3;
cout<<"\n\nFinal c1 value : "<<c1.display( );
cout<<"\nFinal c2 value : "<<c2.display( );
cout<<"\nFinal c3 value : "<<c3.display( );
return 0;
}

The output is:

Initial c1 value : 100
Initial c2 value : 98
Initial c3 value : 94
Final c1 value : 94
Final c2 value : 94
Final c3 value : 94

Within the function parameter we use & to represent a pass by reference. Similarly, here in this program we also return a reference (which is indicated by timer&).

timer& operator =(timer &x)

Now when the compiler encounters:

c1 = c2 = c3;

It will first evaluate:

c2 = c3;

and it will not only assign c3 to c2, but will also return c2 (c2 was the object that invoked the operator member function and ‘this’ refers to c2). The next step will now be:

c1 = c2;

and your program will work perfectly. The overloaded = operator now performs two operations: assignment as well as returning a reference to the object that invoked the overloaded operator function.

Maybe you are wondering, why not return a pointer to type ‘timer’ instead of using references for returning? Again, c2 = c3 will work but the overall expression:

c1 = c2 = c3;

will not work since the expression will reduce to:

c1 = pointer;

and this is wrong!


Go back to the Contents Page 2


Copyright © 2004 Sethu Subramanian All rights reserved.