Wrox Press C++ Tutorial
Operator overloading enables you to make standard operators, such as +, -, * and so on, work with objects of your own data types. It allows you to write a function which redefines a particular operator so that it performs a particular action when it's used with objects of a class. For example, you could redefine the operator > so that, when it was used with objects of the class Box (which we saw earlier), it would return true if the first Box argument had a greater volume than the second.
Operator overloading doesn't allow you to invent new operators, nor can you change the precedence of an operator, so your overloaded version of an operator will have the same priority in the sequence of evaluating an expression as the original base operator.
Although you can't overload all the operators, the restrictions aren't particularly oppressive. These are the operators that you can't overload:
::?:.sizeof.*
Anything else is fair game, which gives you quite a bit of scope. Obviously, it's a good idea to ensure that your versions of the standard operators are reasonably consistent with their normal usage, or at least reasonably intuitive in their operation. It wouldn't be a very sensible approach to produce an overloaded + operator that performed the equivalent of a multiply on class objects. The best way to understand how operator overloading works is to work through an example, so let's implement what we just referred to, the greater than operator, >, for the Box class.
If we want to implement an overloaded operator for a class, we have to write a special function. Assuming that it is a member of the class Box, the prototype for the function to overload the > operator will be as follows:
int operator>(Box& aBox); // Overloaded 'greater than'
The word operator here is a keyword. Combined with an operator, in this case, >, it defines an operator function. The function name in this case is operator>(). You can write an operator function with or without a space between the keyword operator and the operator itself, as long as there's no ambiguity. The ambiguity arises with operators using normal letters such as new or delete. If they are written without a space, operatornew() or operatordelete(), they are legal names for ordinary functions, so for operator functions with these operators, you must leave a space between the keyword operator and the operator itself.
With our operator function operator>(), the right operand of the operator is that which is defined between parentheses. The left operand will be defined implicitly by the pointer this. So, if we have the following if statement,
if(Box1 > Box2)
cout << endl << "Box1 is greater than Box2";
then the expression between parentheses in the if will call our operator function. It is equivalent to this function call:
Box1.operator>(Box2);
The correspondence between the Box objects in the expression and the operator function parameters is illustrated here:

Let's look at how the code for the operator>() function works:
// Operator function for 'greater than' which
// compares volumes of Box objects.
bool Box::operator>(const Box& aBox) const
{
return (this->Volume()) > (aBox.Volume())
}
We use a reference parameter to the function to avoid unnecessary copying when the function is called. Note that we use the keyword const with this parameter, because we only want to compare the objects, we have no intention of altering either one of them. For the same reason, we declare the member function as const - by doing this, we are saying that this particular member function will not change the object on which it acts (the object pointed to by the this pointer), and the compiler will therefore flag any attempt to do so as an error. The return expression uses the member function Volume()to calculate the volume of the Box object pointed to by this, and compares the result, using the basic operator > , with the volume of the object aBox. Thus, true is returned if the Box object pointed to by the pointer this has a larger volume than the object aBox passed as a reference argument, and false otherwise. The member function Volume() also does not change the object on which it acts - it merely uses the length, breadth and height members to calculate the volume of the Box object. So, we should also declare Volume() as a const member function.
In fact, we would get a compile error in the operator>() function if we failed to do this, since we would be passing a const parameter to a function that had not promised not to modify it.
We can exercise this function with an example:
//Ex7_03.cpp
// Exercising the overloaded 'greater than' operator
#include <iostream>
using namespace std;
class Box // Class definition at global scope
{
public:
// Constructor definition
Box(double lv=1.0, double bv=1.0, double hv=1.0)
{
cout << endl << "Constructor called.";
length = lv; // Set values of
breadth = bv; // data members
height = hv;
}
// Function to calculate the volume of a box
double Volume() const
{
return length * breadth * height;
}
// Overloaded 'greater than'
bool operator>(const Box& aBox) const;
// Destructor definition
~Box()
{ cout << "Destructor called." << endl; }
private:
double length; // Length of a box in inches
double breadth; // Breadth of a box in inches
double height; // Height of a box in inches
};
// Operator function for 'greater than' which
// compares volumes of Box objects.
bool Box::operator>(const Box& aBox) const
{
return (this->Volume()) > (aBox.Volume());
}
int main()
{
Box SmallBox(4.0, 2.0, 1.0);
Box MediumBox(10.0, 4.0, 2.0);
Box BigBox(30.0, 20.0, 40.0);
if(MediumBox > SmallBox)
cout << endl
<< "MediumBox is Bigger than SmallBox";
if(MediumBox > BigBox)
cout << endl
<< "MediumBox is Bigger than BigBox";
else
cout << endl
<< "MediumBox is not Bigger than BigBox";
cout << endl;
return 0;
}
The prototype of the operator function operator>() appears in the public section of the class. As the function definition is outside the class definition, it won't default to inline. This is quite arbitrary. We could just as well have put the definition in place of the prototype in the class definition. In this case, we wouldn't need to qualify the function name with Box:: in front of it. As you'll remember, this is needed in order to tell the compiler that the function is a member of the class Box.
The function main() has two if statements using the operator > with class members. These automatically invoke our overloaded operator. If you wanted to get confirmation of this, you could add an output statement to the operator function. The output from this example is:

The output demonstrates that the if statements work fine with our operator function, so being able to express the solution to Box problems directly in terms of Box objects is beginning to be a realistic proposition.
With our operator function operator>(), there are still a lot of things that you can't do. Specifying a problem solution in terms of Box objects might well involve statements such as the following:
if(aBox > 20.0)
Our function won't deal with that. If you try to use an expression comparing a Box object with a numerical value, you'll get an error message. In order to support this, we would need to write another version of the function operator>(), as an overloaded function.
We can quite easily support the type of expression that we've just seen. The prototype of the function would be:
// Compare a Box object with a constant
bool operator>(const double& value) const;
This would appear in the definition of the class. The Box object will be passed as the implicit pointer this.
The implementation is also easy. It's just one statement in the body of the function:
// Function to compare a Box object with a constant
bool Box::operator>(const double& value) const
{
return (this->Volume()) > value;
}
This couldn't be much simpler, could it? But we still have a problem with the operator > with Box objects. We may well want to write statements such as this:
if(20.0 > aBox)
... // do something
You might argue that this could be done by implementing the operator function operator<() and rewriting the statement above to use it, which is quite true. Indeed, the < operator is likely to be a requirement for comparing Box objects anyway, but an implementation of support for an object type shouldn't artificially restrict the ways in which you can use the objects in an expression.
The use of the objects should be as natural as possible. The problem is how to do it. A member operator function always provides the left argument as the pointer this. Since the left argument, in this case, is of type double, we can't implement it as a member function. That leaves us with two choices: an ordinary function or a friend function. Since we don't need to access the private members of the class, we can implement it as an ordinary function. The prototype, placed outside the class definition, would need to be:
bool operator>(const double& value, const Box& aBox) const;
The implementation would be this:
// Function comparing a constant with a Box object
int operator>(const double& value, Box& aBox)
{
return value > aBox.Volume();
}
As we've seen already, an ordinary function (and a friend function for that matter) accesses the public members of an object by using the direct member selection operator and the object name. The member function Volume() is public, so there's no problem using it here.
If the class didn't have the public function Volume(), we could either use a friend function that could access the private data members directly, or we could provide a set of member functions to return the values of the private data members and use those in an ordinary function to implement the comparison.
We can put all this together in an example to show how it works:
//Ex7_04.cpp
// Implementing a complete overloaded 'greater than' operator
#include <iostream>
using namespace std;
class Box // Class definition at global scope
{
public:
// Constructor definition
Box(double lv=1.0, double bv=1.0, double hv=1.0):
length(lv),
breadth(bv),
height(hv)
{
cout << endl << "Constructor called.";
}
// Function to calculate the volume of a box
double Volume() const
{
return length * breadth * height;
}
// Operator function for 'greater than' which
// compares volumes of Box objects.
bool operator>(const Box& aBox) const
{
return (this->Volume()) > (aBox.Volume());
}
// Function to compare a Box object with a constant
bool operator>(const double& value) const
{
return (this->Volume()) > value;
}
// Destructor definition
~Box()
{ cout << "Destructor called." << endl;}
private:
double length; // Length of a box in inches
double breadth; // Breadth of a box in inches
double height; // Height of a box in inches
};
bool operator>(const double& value, const Box& aBox);
int main()
{
Box SmallBox(4.0,2.0, 1.0);
Box MediumBox(10.0, 4.0, 2.0);
if(MediumBox > SmallBox)
cout << endl
<< "MediumBox is Bigger than SmallBox";
if(MediumBox > 50.0)
cout << endl
<< "MediumBox capacity is more than 50";
else
cout << endl
<< "MediumBox capacity is not more than 50";
if(10.0 > SmallBox)
cout << endl
<< "SmallBox capacity is less than 10";
else
cout << endl
<< "SmallBox capacity is not less than 10";
cout << endl;
return 0;
}
// Function comparing a constant with a Box object
bool operator>(const double& value, const Box& aBox)
{
return value > aBox.Volume();
}
The constructor for the class Box has been updated to initialize the data members rather than use assignments, since it's a bit more efficient. Note the position of the prototype for the ordinary function version of operator>(). It needs to follow the class definition, because it refers to a Box object in the parameter list. If you place it before the class definition, the example will not compile.
There is a way to place it at the beginning of the program file following the #include statement: use an incomplete class declaration. This would precede the prototype and would look like this:
class Box; // Incomplete class declaration
bool operator>(const double& value, const Box& aBox);
The incomplete class declaration identifies Box to the compiler as a class and is sufficient to allow the compiler to process the prototype for the function properly, since it now knows that aBox is a variable of a user-defined type to be specified later.
This mechanism is also essential in circumstances where you have two classes, each of which has a pointer to an object of the other class as a member. They will each require the other to be declared first. It's possible to resolve such an impasse through the use of an incomplete class declaration.
The output from the example is:

After the constructor messages due to the declarations of the objects SmallBox and MediumBox, we have the output lines from the three if statements, each of which is working as we expected. The first of these is calling the operator function that is a class member and works with two Box objects. The second is calling the member function that has a parameter of type double. The expression in the third if statement calls the operator function that we implemented as an ordinary function.
As it happens, we could have made both the operator functions which are class members ordinary functions, since they only need access to the member function Volume(), which is public.
Any comparison operator can be implemented in much the same way as we have implemented these. They would only differ in the minor details and the general approach to implementing them would be exactly the same.
If you don't provide an overloaded assignment operator function for your class, the compiler will provide a default. The default version will simply provide a member-by-member copying process, similar to that of the default copy constructor. However, don't confuse the default copy constructor with the default assignment operator. The default copy constructor is called by a declaration of a class object that's initialized with an existing object of the same class, or by passing an object to a function by value. The default assignment operator, on the other hand, is called when the left hand side and the right hand side of an assignment statement are objects of the same class.
For our Box class, the default assignment operator works with no problem, but for any class which has dynamically allocated members, you need to look carefully at the requirements of the class in question. There may be considerable potential for chaos in your program if you leave the assignment operator out under such circumstances.
For a moment, let's return to our Message class that we used when talking about copy constructors. You'll remember it had a member, pmessage, which was a pointer to a string. Now consider the effect that the default assignment operator could have. Suppose we had two instances of the class, Motto1 and Motto2. We could try setting the members of Motto2 equal to the members of Motto1 using the default assignment operator, as follows:
Motto2 = Motto1; // Use default assignment operator
The effect of using the default assignment operator for this class is essentially the same as using the default copy constructor: disaster will result! Since each object will have a pointer to the same string, if the string is changed for one object, it's changed for both. There's also the problem that when one of the instances of the class is destroyed, its destructor will de-allocate the memory used for the string and the other object will be left with a pointer to memory that may now be used for something else.
What we need the assignment operator to do is to copy the text to a memory area owned by the destination object.
We can fix this with our own assignment operator, which we will assume is defined within the class definition:
// Overloaded assignment operator for Message objects
Message& operator=(const Message& aMess)
{
// Release memory for 1st operand
delete[] pmessage;
pmessage = new char[ strlen(aMess.pmessage) + 1];
// Copy 2nd operand string to 1st
strcpy(this->pmessage, aMess.pmessage);
// Return a reference to 1st operand
return *this;
}
You need to take note of a couple of subtleties here. The first is that we return a reference from the operator function. This is vital for two reasons.
First, we might need to use the result of the assignment operator on the right hand side of an expression. Consider a statement such as,
Motto1 = Motto2 = Motto3;
which will translate into,
Motto1 = (Motto2.operator=(Motto3));
with the result of the second operator function call on the right of the first equals sign. This, in turn, will finally become this:
Motto1.operator=(Motto2.operator=(Motto3));
Thus operator=() must return a usable value of the correct type.
The second reason (and the reason that we must return a reference) is that we may wish to use the result of the assignment on the left of another assignment. We discussed this principle in the previous chapter. Consider the following example:
(Motto1 = Motto2) = Motto3;
This translates into,
(Motto1.operator=(Motto2)) = Motto3;
and ultimately becomes:
(Motto1.operator=(Motto2)).operator=(Motto3);
The only way to cope with both of these situations in a standard and meaningful way is to return a reference.
Note that the C++ language does not enforce any restrictions on the accepted parameter or return types for the assignment operator, but it makes sense to declare the operator in the way just described.
The second subtlety you need to remember is that each object already has memory for a string allocated, so the first thing that the operator function has to do is to delete the memory allocated to the first object and then re-allocate sufficient memory to accommodate the string belonging to the second object. Once this is done, the string from the second object can be copied to the new memory now owned by the first.
There's still a defect in this operator function. What if we were to write the following statement?
Motto1 = Motto1;
Obviously, you wouldn't do anything as stupid as this directly, but it could easily be hidden behind a pointer, for instance, as in the following statement,
Motto1 = *pMess;
where the pointer pMess points to Motto1. In this case, the operator function as it stands would delete the memory for Motto1, allocate some more memory based on the length of the string that has already been deleted and try to copy the old memory which, by then, could well have been corrupted. We can fix this with a check at the beginning of the function, so now it would become this:
// Overloaded assignment operator for Message objects
Message& operator=(const Message& aMess)
{
if(this == &aMess) // Check addresses, if equal
return *this; // return the 1st operand
// Release memory for 1st operand
delete[] pmessage;
pmessage = new char[ strlen(aMess.pmessage) +1];
// Copy 2nd operand string to 1st
strcpy(this->pmessage, aMess.pmessage);
// Return a reference to 1st operand
return *this;
}
Let's put this together in a working example. We'll add a function, called Reset(), to the class at the same time. This just resets the message to a string of asterisks.
// Ex7_05.cpp
// Overloaded copy operator perfection
#include <iostream>
#include <string>
using namespace std;
class Message
{
private:
char* pmessage; // Pointer to object text string
public:
// Function to display a message
void ShowIt(void)
{
cout << endl << pmessage;
}
//Function to reset a message to *
void Reset(void)
{
char* temp=pmessage;
while(*temp)
*(temp++)='*';
}
// Overloaded assignment operator for Message objects
Message& operator=(const Message& aMess)
{
if(this == &aMess) // Check addresses, if equal
return *this; // return the 1st operand
// Release memory for 1st operand
delete[] pmessage;
pmessage = new char[ strlen(aMess.pmessage) +1];
// Copy 2nd operand string to 1st
strcpy(this->pmessage, aMess.pmessage);
// Return a reference to 1st operand
return *this;
}
// Constructor definition
Message(const char* text = "Default message")
{
// Allocate space for text
pmessage = new char[ strlen(text)+1 ];
strcpy(pmessage, text); // Copy text to new memory
}
// Destructor to free memory allocated by new
~Message()
{
// Just to track what happens
cout << "Destructor called."
<< endl;
delete[] pmessage; // Free memory assigned to pointer
}
};
int main()
{
Message Motto1("The devil takes care of his own");
Message Motto2;
cout << "Motto2 contains - ";
Motto2.ShowIt();
cout << endl;
Motto2 = Motto1; // Use new assignment operator
cout << "Motto2 contains - ";
Motto2.ShowIt();
cout << endl;
Motto1.Reset(); // Setting Motto1 to * doesn't affect Motto2
cout << "Motto1 now contains - ";
Motto1.ShowIt();
cout << endl;
cout << "Motto2 still contains - ";
Motto2.ShowIt();
cout << endl;
return 0;
}
You can see from the output of this program that everything works exactly as required, with no linking between the messages of the two objects, except where we explicitly set them equal.

So let's have another golden rule out of all of this:
Always implement an assignment operator if you allocate space dynamically for a data member of a class.
Having implemented the assignment operator, what happens with operations such as +=? Well, they don't work unless you implement them. For each form of op= that you want to use with your class objects, you need to write another operator function.
Let's look at overloading the addition operator for our Box class. This is interesting because it involves creating and returning a new object. The new object will be the sum (whatever we define that to mean) of the two Box objects that are its operands.
So what do we want the sum to mean? Let's define the sum of two Box objects as a Box object which is large enough to contain the other two boxes stacked on top of each other. We can do this by making the new object have a length member, which is the larger of the length members of the objects being added, and a breadth member derived in a similar way. The height member will be the sum of the height members of the two operand objects, so that the resultant Box object can contain the other two Box objects. This isn't necessarily an optimal solution, but it will be sufficient for our purposes. By altering the constructor, we'll also arrange that the length member of a Box object is always greater than or equal to the breadth member.
The addition operation is easier to explain graphically, so it's illustrated below:

Since we need to get at the members of an object directly, we will make the operator+() a member function. The prototype of the function will be this:
// Function adding two Box objects
Box operator+(const Box& aBox) const;
We define the parameter as a const reference to avoid unnecessary copying of the right argument when the function is called. We also declare the function itself to be const, as we are not going to change in anyway the object that appears on the left hand side of the addition operator. The operation function definition would now be as follows:
// Function to add two Box objects
Box Box::operator+(const Box& aBox) const
{
// New object has the larger length and breadth,
// and sum of heights
return Box(length>aBox.length? length:aBox.length,
breadth>aBox.breadth? breadth:aBox.breadth,
height+aBox.height);
}
We can see how this works in an example:
// Ex7_06.cpp
// Adding Box objects
#include <iostream>
using namespace std;
class Box // Class definition at global scope
{
public:
// Constructor definition
Box(double lv=1.0, double bv=1.0, double hv=1.0):
height(hv)
{
length = lv > bv? lv: bv; // Ensure that
breadth = bv < lv? bv: lv; // length >= breadth
}
// Function to calculate the volume of a box
double Volume() const
{
return length * breadth * height;
}
// Operator function for 'greater than' which
// compares volumes of Box objects.
bool operator>(const Box& aBox) const
{
return (this->Volume()) > (aBox.Volume());
}
// Function to compare a Box object with a constant
bool operator>(const double& value) const
{
return Volume() > value;
}
// Function to add two Box objects
Box operator+(const Box& aBox) const
{
// New object has the larger length & breadth,
// and sum of the heights
return Box(length>aBox.length? length:aBox.length,
breadth>aBox.breadth? breadth:aBox.breadth,
height + aBox.height);
}
// Function to show the dimensions of a box
void ShowBox(void) const
{
cout << length << " " << breadth << " " << height
<< endl;
}
private:
double length; // Length of a box in inches
double breadth; // Breadth of a box in inches
double height; // Height of a box in inches
};
bool operator>(const double& value, const Box& aBox);
int main(void)
{
Box SmallBox(4.0, 2.0, 1.0);
Box MediumBox(10.0, 4.0, 2.0);
Box aBox;
Box bBox;
aBox = SmallBox+MediumBox;
cout << "aBox dimensions are ";
aBox.ShowBox();
bBox = aBox+SmallBox+MediumBox;
cout << "bBox dimensions are ";
bBox.ShowBox();
return 0;
}
// Function comparing a constant with a Box object
bool operator>(const double& value, const Box& aBox)
{
return value > aBox.Volume();
}
In this example we've changed the Box class members a little. The destructor has been deleted as it isn't necessary for this class, and the constructor has been modified to ensure that the length member isn't less than the breadth member. Knowing that the length of a box is always at least as big as the breadth makes the add operation a bit easier. We've also added the function ShowBox() to output the dimensions of a Box object. This will enable us to verify that our overloaded add operation is working as we expect.
The output from this program is:

This seems to be consistent with the notion of adding Box objects that we have defined and, as you can see, the function also works with multiple add operations in an expression. For the computation of bBox, the overloaded addition operator will be called twice.
We could equally well have implemented the add operation for the class as a friend function. Its prototype would then be this:
friend Box operator+(const Box& aBox, const Box& bBox);
The method to produce the result would be much the same, except that you'd need to use the direct member selection operator to obtain the members of the arguments to the function. It would work just as well as the first version of the operator function.