Operator overloading.

C++ language allows programmers to give new meanings to operators. This is called operator overloading. This is a very powerful feature, but you need to be careful with using it because your programs may become hard to read due to non-traditional operator definition. The advantages of overloading operators are clear:

There are some limitations in operator overloading:

The table below contains the list of operators that can be overloaded by programmers.
Overloadable operators
+ - * / % ^ &
| ~ ! = < > +=
-= *= /= %= ^= &= |=
<< >> >>= <<= == != <=
>= && || ++ -- ->* ,
-> [] () new new[] delete delete[]

Operator functions and operator methods

C++ provides two ways of overloading operators. Functions overloading operators can be defined either as simple functions separated from any class definition or as members of a class. The following example shows how to overload operator + (plus) for two objects of the Polynomial:
Polynomial operator+(const Polynomial& p, const Polynomial& q)
{
   int max_degree = p.degree>q.degree ? p.degree : q.degree; 
   Polynomial res( max_degree );
   
   for(int i=max_degree;i>=0;i--)
      res.a[i] = (i<=p.degree?p.a[i]:0) + (i<=q.degree?q.a[i]:0);
   return res;
}
where the class Polynomial can be defined as
class Polynomial {
private:
   int  degree;
   int *a;      // coefficients   
public:
   Polynomial();
   Polynomial(int d);
   virtual ~Polynomial() { delete[] a; };
   // overloading operators
   friend Polynomial operator+(const Polynomial& p, const Polynomial& q);
};

Polynomial::Polynomial() : degree(0)
{
   a = new int[1];
   a[0] = 0;
}

// creates polynomial "x^d"
Polynomial::Polynomial(int d)
{
   degree = d>=0 ? d : 0;
   a = new int[degree+1];
   a[degree] = 1;
   for(int i=0;i<degree;i++)
      a[i] = 0;
}
please note that in the class definition we specified the stand alone function overloading operator as a friend of the class to provide access to private variables. After overloading operator this way the following code
a = p + q;
where all variables are of the Polynomial type will be executed as:
a = operator+(p, q);
We also would like to note that not all operators can be overloaded as separate functions. For example, assignment operators, function call operator, index operator cannot be.

Another way to overload an operator is to define the function overloading an operator as a member of the class. Here is an example that overloads operator - (minus) for the same class:

class Plynomial {
...
public:
   ...
   Polynomial operator-(const Polynomial& q) const;
};

Polynomial Polynomial::operator-(const Polynomial& q) const
{
   int max_degree = degree>q.degree ? degree : q.degree; 
   Polynomial res( max_degree );
   
   for(int i=max_degree;i>=0;i--)
      res.a[i] = (i<=degree?a[i]:0) - (i<=q.degree?q.a[i]:0);
   return res;
}
If we overloaded the subtraction operator as a class member, the following code
a = b - c;
is taken as
a = b.operator-(c);
Please note that this operator does not change the value of the Polynomial b, instead it creates a new Polynomial res equal to the difference between b and c and returns it as the value.

The essential difference between member operators and function operators is that member operator is considered as object.operator( parameter ). That is, the first argument always has to be an object of the class we overload this operator for. For example, we can easily overload operator * to multiply a polynomial by an integer number

Polynomial Polynomial::operator*(int k)
{
   Polynomial res = *this;
   for(int i=0;i<=degree;i++) res.a[i] *= k;
   return res;
} 
but we cannot overload similarly overload operator * to multiply an integer number by a polynomial, because the code i * p will be treated as
i.operator*(p)
where i is an integer variable. In such cases we need to overload these operators as separate functions:
Polynomial operator*(int k, const Polynomial& p)
{
   Polynomial res = p;
   for(int i=0;i<=res.degree;i++) res.a[i] *= k;
   return res;
} 

Assignment operators

As we mentioned earlier, assignment operators (=, +=, -=, etc) cannot be overloaded as separate functions. They must be members of the class. Let's define the assignment operator for the Polynomial class:
Polynomial& Polynomial::operator=(const Polynomial& p)
{
   delete[] a; // we are changing the value of the current polynomial => clear the memory
   degree = p.degree;
   a = new int[degree+1];
   for(int i=0;i<=degree;i++) a[i] = p.a[i];
   return *this;
}
Now, the same code
a = b + c;
will be considered by the compiler as
a.operator=( operator+( b, c) );
Please note that the assignment operator return the reference to the object invoking the operator. It is done to be able to write code like a = b = c.

To overload a compound assignment operator like += we do almost the same:

Polynomial& Polynomial::operator+=(const Polynomial& p)
{
   int i;
   if( degree < p.degree ){
      int *t = new int[p.degree+1];
      for(i=0;i<=p.degree;i++) 
         t[i] = (i<=degree ? a[i] : 0);
      degree = p.degree;
      delete[] a;
      a = t;
   } 
   for(i=0;i<=degree;i++) a[i] += p.a[i];
   return *this;
}

Overloading unary operators

Some operators have both binary and unary meaning. For example, operator - can be used as a subtraction operator or as a negation operator. We have seen how to overload the binary meaning of the operator, to overload the unary operator, all we need to do, is to decrease the number of argument it takes. For example, to overload the unary minus as a separate function we can do this
Polynomial operator-(const Polynomial& p)
{
   Polynomial res = p;
   for(int i=0;i<=res.degree;i++) 
      res.a[i] = - res.a[i];
   return res;
} 
to overload it as a member operator we need to do this
Polynomial Polynomial::operator-()
{
   Polynomial res = *this;
   for(int i=0;i<=res.degree;i++) 
      res.a[i] = - res.a[i];
   return res;
} 

Overloading prefix and postfix operators

Some operators can be used in both prefix and postfix forms. For example, we can often see something like i++ or a[++i]. To overload the prefix increment operator, we define it as a member function with no arguments:
Polynomial& Polynomial::operator++()
{
   a[0]++;
   return *this;
}
Please note that this operator returns a reference to the object invoking the operator. This allows users to put this operator on the left side of an assignment operator.

To overload a postfix operator, we need to declare a function that takes one dummy argument (which we won't be using) to distinguish it with a prefix form:

Polynomial Polynomial::operator++(int dummy)
{
   Polynomial res = *this;
   a[0]++;
   return res;
}
Note that to keep the usual meaning of the operator (first return the old value and then increment) we need to store the current value and then return it. Because of this, we return a value not a reference.

After the overloading the following code:

p++;
++p;
will be considered as
p.operator++( 0 /* dummy argument */);
p.operator++(  );

Overloading operators << and >>

There is no difference between overloading the shift operators << and >> and any other arithmetic operator. However, in C++ these operators are usually considered as output operators. That is, it would be logical to think that line
Polynomial p(3);
cout<<p;
will print the polynomial p to the standard output. To do this, we need to overload the shift operator << to make the operator work with the left argument of the ostream type and the right argument of the Polynomial type. Obviously, this operator has to be overloaded as a separate function:
ostream& operator<<(ostream& out, const Polynomial& p)
{
   for(int i=p.degree; i>=0; i++){
      if( p.a[i] == 0 ) continue;
      out<<(i<p.degree&&p.a[i]>0?"+":"")<<p.a[i];
      if( i > 0 ){ 
         out<<"*x";
         if( i > 1 ) out<<"^"<<i;
      }
   }
   return out;
}
please note that we return the left argument as the value of the function to be able to use the operator in a sequence:
cout<<"p = "<<p<<endl;
We can similarly overload the input operator, but since the code will be more complicated we decided to demonstrate only the declaration:
class Polynomial {
   ...
   public:
      ...
      friend ostream& operator<<(ostream& out, const Polynomial& p);
      friend istream& operator>>(istream& inp, const Polynomial& p);
};

Overloading the subscript and function call operator

In this section we would like to overload two more operators from the
list. Namely, the subscript [] operator and function call operator (). Both of these operators can be implemented only as members of a class. Let's define the square brackets operator for the class Polynomial to let the users access to the coefficients of polynomials:
int Polynomial::operator[](int index)
{
   if( index<0 || index>degree )
      throw out_of_range("index in the Polynomial [] operator is out of range");
   return a[index];
}
please note that our method returns an integer value. By doing so we allow programmers read-only access to the coefficients. That is, they can do this:
cout<<p[4];
but they cannot do this
p[5] = 4; 
In order to provide the read-write access to the coefficients we need to return not an integer but a reference to the integer value:
int& Polynomial::operator[](int index)
{
   if( index<0 || index>degree )
      throw out_of_range("index in the Polynomial [] operator is out of range");
   return a[index];
}
However, providing such an access may cause problems with the degree of polynomials. Since we try to keep the highest degree coefficient non zero, and letting users write access to it is not wise.

If we redefine the function call operator, we will be able to do something like this:

cout<<"p(3) = "<<p(3);
Of course, in this case it makes sense to define the () operator in such a way that it computes the value of the polynomial at the given point. Here is an example:
double Polynomial::operator()(double x) const
{
   double res = 0;
   for(int i=degree; i>=0; i++)
      res = res * x + a[i];
   return res;
}
This operator has one interesting feature: this is the only operator we can change the number of arguments for. In other words, we can overload the function call operator and make it take two or three arguments. This feature can be useful if we create a class that implements a multidimensional array. Unfortunately, we are not allowed to change the number of arguments for the subscript operator. That is, this marray[1, 3] would be a wrong construct, but we can change the number of arguments for the () operator, so we can use marray(1, 3) instead.

explicit constructors

Now let's try to use some of our operators for Polynomial class. Let's execute the following code:
#include <iostream>
#include "polynomial.h"
using namespace std;

void main()
{
   Polynomial x(1), x2(2), x3(3), p;
   p = 2*x3 - 3*x2 + x;
   cout<<p<<"\n";
   p = p + 4;
   cout<<p<<"\n";
}
In line 8 we use the multiplication operator, addition operator, and subtraction operator. After this line, the value of the polynomial p becomes 2x3-3x2+x. After executing line 10, it would be reasonable to expect that the value of the polynomial would be 2x3-3x2+x+4. However, the output operator on the next line prints: x4+2x3-3x2+x. Why does this happen?

To answer this question we need to understand what happens when a compiler sees the line

   p = p + 4;
Since we did not overload operator + that takes one polynomial argument and one integer argument, the program has to use operator plus that takes two polynomial arguments. But the second argument is not a polynomial, in order to make it a polynomial we need to convert an integer into a polynomial. Since we have a constructor that takes only one integer, the compiler will implicitly use this constructor to perform the conversion. Thus, the compiler takes this instruction as:
p.operator=( operator+( p, Polynomial(4)) );
But the constructor Polynomial(4) actually creates polynomial x4 and this explains the strange result of our program.

One way to solve a problem like this is to prohibit the compiler implicitly execute constructors. In order to do so, we need to use keyword explicit in front of a constructor name. For example, if in the class definition we have:

class Polynomial {
   private:
      ...
   public:
      Polynomial() ... ;
      explicit Polynomial(int d) ... ;
      ...
};
then during the compilation time we would receive an error message that operator + (Polynomial, int) is not defined. Of course, to be able to perform the addition we still need to overload operator plus for Polynomial and integer, but now we now how to do that. Don't we?

Example: smart pointer class

At the last section I would like to discuss an example from the Nicolai Josuttis text book. This example, shows not only how we can overload operators * (unary) and ->, but also explains an idea behind garbage collectors. In the example we develop a class of smart pointers. In other words, we will define a new class, and overload operators * and -> in such a way, that objects of the class could look like normal pointers (similar to iterators for STL container classes).

If we consider the code like

{
   Robot *robot[10];
   for(int i=0;i<10;i++)
      robot[i] = new Robot();
}
then we will execute the constructor for the Robot class 10 times, but we don't execute a destructor even once despite the fact that our pointers are no longer exist once we left the {} block. You can study the example program to see it. This happens, because we don't actually declared objects in a block, we just declared pointers. Then we allocated memory for the objects, but lost pointers to the memory. This behavior is not always appropriate and may result in wasting resources. In order to avoid such a behavior we will develop a new smart pointers. An object of the new RobotPtr class will contain two pieces of information: However, if we define a class RobotPtr as
class RobotPtr {
   private:
      Robot *ptr;
      int    counter;
   public:
      ...
};
we will run into a problem, ho to update the counter inside all pointers to a particular instance of the Robot class, when we create or destroy an smart pointer to the very same instance. Shortly speaking we want all instances of the RobotPtr class that point to the same Robot object share the counter. To achieve this, we will declare a counter field as a pointer to an integer. Thus, the complete definition of the new class could look like:
class RobotPtr {
   private:
      Robot *ptr;
      int   *counter;
   public:
      RobotPtr(Robot *p=NULL);
      ~RobotPtr();
      RobotPtr& operator=(const RobotPtr& p);
      const RobotPtr& operator*();
      Robot* operator->();
};
You can find the complete implementation on the example page.

References