Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C

Three important operators you often need to overload

4.70/5 (73 votes)
26 Aug 2008CPOL5 min read 1  
A look at three important operators you often need to overload.

Introduction

Almost in all C++ books that we follow, there were mentioned a few really important points about operator overloading. Unfortunately, most of us either overlook those, or don’t consider those points important enough while writing code. From my real life experience, I am going to submit five important areas about “Operator Overloading”. Hope you will find this article interesting.

Using the code

Point No: 1 ==> It does not matter to overload an operator as a global function or as a member function unless and until the “left hand side” operands are actually an object of your “to be operated” class.

A few people argued that since for some cases we must need to overload an operator as a global function, we should use a common programming structure, i.e., overload all the operators as a global function. That is really a programmer’s choice. It was never ever a mandatory stuff to do. As long as the operands of the LHS of the operator are actually an object of the “to be operated” class, you are all set to overload that operator as a member function. And, that is indeed a good practice, the only difference is that it needs less number of arguments (if you define the operator overload as global, the first argument you need to pass is a reference to the desired class over which the operator will work).

That is, if you are trying to overload a unary operator (as a member function), you don’t need to pass an argument at all; for a binary operator, only a single argument will suffice.

Consider:

C++
#include <iostream>

class complex{
  private:
    double re,im;
  public:
   complex(double=0.0,double=0.0);
   complex operator+(complex);
   complex operator*(complex);
};

Here, our goal is to overload two binary operators: addition(+) and multiplication(*). An implicit “this” will suffice our needs for the “Left Hand side” operands. You need to only pass the RHS operands as an argument.

Point No: 2 ==> You must overload an operator as a global function when the “left hand side” operands are beyond your control, but what about friends?

Famous example for this??? Yes, you are right, the stream insertion (<<) and stream extraction (>>) operators. In order to overload these operators in our designed class (so that an object of our own class can be used with cout and/or cin the same way as primitives used to do), first, we need to understand how these operators actually work. Consider the following:

C++
#include <iostream>
int main()
{
 int var=10;
 std::cout<<var;
 return 0;
}

Here, the LHS operand has the type ostream&, as in std::cout <<var;. And, the RHS operand is a primitive. Thus, if we want to do the same for our own class, the LHS operand will remain the same, i.e., an ostream&, and the RHS operand will be the object of our own class.

Now, we can only overload an operator as a member function if and only if the LHS operand is an object of ours. So, to overload a << operator as a member function, it has to be a member of the ostream class. Now think, is it at all possible? Do we allow modifying a standard given by C++ STL? The simple answer is “Never Ever”.

The same logic stands for the “Stream Extraction” (>>) operator, the only difference is here the LHS operand is an “istream&”.

So, up to this is very much clear. We agree about using a global function for overloading the “Stream Insertion” and “Stream Extraction” operators. Naturally, this function is not a part of the class’s integrity, and hence this function will not have any access to the class’ private members. That’s insane. If that function doesn’t have any access to the private members of the class, then what good is this operator? Useless.

Thanks to Mr. Bjarne Stroustrup for giving us a wonderful solution: Friend functions. And, yes, if we just make that function as a friend, we are all set. Now, we still use a global function, but since it is a “friend” to the class, the class will happily give it access to its private members. So, always use the following structure:

#include <iostream>
using std::cout;
using std::cin;
using std::ostream;
using std::istream;
class complex{
  friend ostream& operator<<(ostream&, const complex&);
  friend istream& operator>>(istream&, complex&);
  private:
   double re,im;
  public:
   complex(double=0.0,double=0.0);
   complex operator+(complex);
   complex operator*(complex);
};
ostream& operator<<(ostream& mystream, const complex& cmp){
 mystream<<cmp.re;
 mystream<<cmp.im;
 return mystream;
}
istream& operator>>(istream& mystream, complex& cmp)
{
 mystream>>cmp.re;
 mystream>>cmp.im;
 return mystream;
}

Do you really and must need to use a global function here? Now, here you have a choice. If your class is designed in such a way that it has getter methods (accessor) for all the members you are looking for in its public interface, you can use those methods via object/object reference in the global function. You don’t need to specify your operator overloading function as a friend to that class.

Point No: 3 ==> Are you planning to overload a subscript operator([ ])? If so keep in mind the cases “Modifiable lvalue” and “Constant rvalue”.

Suppose you are planning to write an advance array class, say with a new feature “boundary checking” (the default array doesn’t have this feature). One most common operator you need to overload is the Subscript operator ([ ]). Although it looks simple, overloading this operator without concentration leads to a very famous and popular bug: guess which one.

A subscript operator can be used in two ways:

  1. If it is used in the left hand side of the assignment (=), we are intending to use the bucket in the index position, and we are intending to put a new value into that placeholder. Got it? Hence, somehow, we need a full write access to that index position. In C++ terminology, you must have a “Reference Access” for that bucket position. That is, we need to have a modifiable “lvalue” access.
  2. If it is used on the right hand side of the assignment (=) or as a standalone (say in a cout statement), we are planning to use a const “rvalue”.

So, we must have to have two different versions of this operator while overloading it.

// Version one, returning modifiable l-vaue
int& Array::operator[](int index)
{
 if(index<0 || index>=size)
   {
     cout<<"Bad Index...........exiting..."<<endl;
     exit(1);
   }
 else
   return ptr[index];
}
//version two, returning constant r-value
int Array::operator[](int index) const
{
 if(index<0 || index>=size)
   {
     cout<<"Bad Index...........existing..."<<endl;
     exit(1);
   }
 else
   return ptr[index];
}

References:

  1. C++ Programming Language: Bjarne Stroustrup
  2. C++ How to Program

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)