Topic : Operator Overloading
Author : Danny Kalev
Page : << Previous 3  
Go to page :


does not have operator += intentionally.

Returning Objects by Value
For the sake of efficiency, large objects are usually passed to -- or returned from -- a function by reference or by their address. There are, however, a few circumstances in which the best choice is still to return an object by value. Operator + is an example of this situation. It has to return a result object, but it cannot modify any of its operands. The seemingly natural choice is to allocate the result object on the free store and return its address. Nevertheless, this is not such a good idea. Dynamic memory allocation is significantly slower than local storage. It also might fail and throw an exception, which then has to be caught and handled. Even worse, this solution is error prone because it is unclear who is responsible for deleting this object -- the creator or the user?

Another solution is to use a static object and return it by reference. For example

class Year
{
private:
  int year;
public:
  Year(int y = 0) : year(y) {}
  Year& operator + (const Year& other) const; //returns a reference to
                                              //a local static Year
  int getYear() const;
  void setYear(int y);
};
Year& Year::operator + (const Year& other) const  
{
  static Year result;
  result = Year(this->getYear() + other.getYear() );
  return result;
}


Static objects solve the ownership problem, but they are still problematic: On each invocation of the overloaded operator, the same instance of the static object is being modified and returned to the caller. The same object can be returned by reference to several more users, who do not know that they are holding a shared instance that has just been modified behind their back.

Finally, the safest and most efficient solution is still to return the result object by value:

class Year
{
private:
  int year;
public:
  Year(int y = 0) : year(y) {}
  Year operator + (const Year& other) const; //return Year object by value
  int getYear() const;
  void setYear(int y);
};
Year Year::operator + (const Year& other) const
{
  return Year(this->getYear() + other.getYear() );  
}


Multiple Overloading
Overloaded operators obey the rules of function overloading. Therefore, it is possible to overload an operator more than once. When is it useful? Consider the following Month class and its associated operator ==:

class Month
{
private:
  int m;
public:
  Month(int m = 0);
};
bool operator == (const Month& m1, const Month &m2);


It is possible to use the overloaded operator == to compare a plain int value and a Month object due to the implicit conversion of int to Month. For example

void f()
{
  int n = 7;
  Month June(6);
  bool same =
    (June == n); //calls bool operator == (const Month& m1, const Month &m2);
}


This works fine, but it i's inefficient: The argument n is first converted to a temporary Month object, which is then compared with the object June. You can avoid the unnecessary construction of a temporary object by defining additional overloaded versions of operator ==:

bool operator == (int m, const Month& month);
bool operator == (const Month& month, int m);


Consequently, the expression June == n will now invoke the following overloaded operator:

bool operator == (const Month& month, int m);

This overloaded version does not create a temporary object, so it's more efficient. The same performance considerations led the C++ Standardization committee to define three distinct versions of operator == for std::string (see Chapter 10, ""STL and Generic Programming"") and other classes of the Standard Library.'

Overloading Operators for Other User-Defined types
You can overload an operator for enum types as well. For example, it may be useful to overload operators such as ++ and -- so that they can iterate through the enumerator values of a given enum type. You can do it like this:

#include <iostream>
using namespace std;
enum Days
{
  Monday,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday,
  Sunday
};
Days& operator++(Days& d, int)  // postfix ++
{
  if (d == Sunday)
    return d = Monday; //rollover
  int temp = d; //convert to an int
  return d = static_cast<Days> (++temp);
}
int main()
{
Days day = Monday;
for (;;) //display days as integers
{
   cout<< day <<endl;
   day++;
   if (day == Sunday)
     break;
}
return 0;
}


If you prefer to view the enumerators in their symbolic representation rather than as integers, you can overload the operator << as well:

ostream& operator<<(ostream& os, Days d) //display Days in symbolic form
{
  switch
  {
  case Monday:
    return os<<"Monday";
  case Tuesday:
    return os<<"Tuesday";
  case Wednesday:
    return os<<"Wednesday";
  case Thursday:
    return os<<"Thursady";
  case Friday:
    return os<<"Friday";
  case Saturday:
    return  os<<"Satrurday";
  case Sunday:
    return os<<"Sunday";
  default:
    return os<<"Unknown";
  }
}


Overloading the Subscripts Operator
For various classes that contain arrays of elements, it's handy to overload the subscript operator to access a single element. Remember always to define two versions of the subscript operator: a const version and a non-const version. For example

class Ptr_collection
{
private :
void **ptr_array;
int elements;
public:
  Ptr_collection() {}
  //...
  void * operator [] (int index) { return ptr_array[index];}
  const void * operator [] (int index) const { return ptr_array[index];}
};
void f(const Ptr_collection & pointers)
{
  const void *p = pointers[0]; //calls const version of operator []
  if ( p == 0)
    return;
  else
  {
    //...use p
  }
}


Function Objects
A function object is implemented as a class that contains an overloaded version of the function call operator. An instance of such a class can be used just like a function. Ordinary functions can have any number of arguments; therefore, operator () is exceptional among other operators because it can have an arbitrary number of parameters. In addition, it can have default arguments. In the following example, a function object implements a generic increment function:

#include <iostream>
using namespace std;
class increment
{
  //a generic increment function
  public : template < class T > T operator()  (T t) const { return ++t;}
};
void f(int n, const increment& incr)
{
  cout << incr(n); //output 1
}
int  main()
{
  int i = 0;
  increment incr;
  f(i, incr);
  return 0;
}


Conclusions
The concept of operator overloading is neither new nor C++ exclusive. It is one of the most fundamental facilities for implementing abstract data types and compound classes. In many ways, overloaded operators in C++ behave like ordinary member functions: They are inherited, they can be overloaded more than once, and they can be declared as either nonstatic members or as nonmember functions. However, several restrictions apply to overloaded operators. An overloaded operator has a fixed number of parameters, and it cannot have default arguments. In addition, the associativity and the precedence of an operator cannot be altered. Built-in operators have an interface, consisting of the number of operands to which the operator can be applied, whether the operator modifies any of its operands, and result that is returned by the operator. When you are overloading an operator, it is recommended that you conform to its built-in interface.

Conversion operators are a special type of overloaded operators. They differ from ordinary overloaded operators in two respects: They do not have a return value and they do not take any arguments.


Page : << Previous 3