7.1 Operator Overloading


Operator overloading refers to the ability to define a new meaning for an existing (built-in) "operator" The list of "operators" includes mathematical operators(+, -, *, /, ++, etc), relational operators ( <, >, ==, etc), logical operators (&&, ||, etc.), access operators ([], ->), assignment operator (=), stream I/O operators ( <<, >>), type conversion operators and several others. While all of these operator have a predefined and unchangeable meaning for the built-in types, all of these operators can be given a specific interpretation for different classes or combination of classes. C++ is particularly generous in the flexibility given to programmers in extending these built-in operators; not all object-oriented languages allow this.

There are a number of reasons why a class designer may decide to provide extensions to one or more of the built-in operators:

natural, suggestive usage

the most natural way to convey the intended meaning of an operation may be through the predefined operators. For example, in defining a class to represent complex or rational numbers the best way to represent adding to complex numbers or adding two rational numbers is by giving a new (extended) meaning to the "+" operator rather than to invent a member function with a suggestive name ("addTo").

semantic integrity

classes that have pointers to objects frequently need a specialized assignment operator in order to copy the objects being pointed to. The failure to properly handle assignment, as was seen earlier with the String class, leads to either memory leaks or run-time errors.

uniformity with base types

templates often impose requirements on their arguments that can only be met by both built-in types and user defined types when the user defined types provided overloaded operations. For example, a Set template may require that its instantiating type have an equality operator ("==") in order to implement the test for membership in the Set.

In many cases, the use of overloaded operators servers some or all of these purposes simultaneously.

A simple example introducing operator overloading is a "safe" array of integers. The array is to be "safe" in the sense that the subscripting operation will insure that the subscript is within bounds. If the subscript is out of bounds, the program will terminate. The declaration of the safe array is:

   class Array {
    private:
      int array[20];
    public:
           Array(int init = 0);
      int& operator[](int i);	// overloaded subscript operator
          ~Array();
   };
In this case, the subscripting operator, referred to as "operator[]" is defined to take a single integer input argument. The subscripting operator returns a reference to the subscripted element of the array after checking that the element of the array exits. This class is intended to be used as follows:
   Array a;

   a[0] = 1;
   a[1] = 1;
   for (int i = 2; i < 20; i++)		// compute first 20 Fibonnaci numbers
       a[i] = a[i-1] + a[i-2];
The compiler, upon encountering the expression "a[0]" will check to see if the class of "a" (in this case Array) contains a class-specific definition for the "[]" operator. Since the Array class contains such an overloaded operator method, the compiler will arrange for code to generated that is the equivalent of:
        a.operator[](0)
which matches with the definition of the method given in the Array class. Similarly, the statement:
       a[i] = a[i-1] + a[i-2];

would be treated as:
	a.operator[](i) = a.operator[](i-1) + a.operator[](i-2);
Since the overloaded subscript operator returns a reference (specifically an "int&"), it is legitimate to have the subscript operator appear in an expression on the left-hand side of the assignment operator.

Notice that aside from its declaration, the Array object looks and feels like a built-in array type. The similarity between them is also suggested by the following code that shows Arrays and built-in arrays being intermixed:

   Array safe;
   int   regular[20];

    // define contents of arrays safe and unsafe

    regular[10] = safe[10];
    safe[11]    = regular[11];
    safe[0]     = safe[0] + regular[0]
The built-in array type and the Array act the same except that the Array will cause a clean and informative error message when a subscript out-of-bounds problem arises.

The implementation of the operator[] method would be written as follows:

   int& Array::operator[](int i) {
        assert(0 <= i && i < 20);
        return array[i];
   }
Again notice that there is nothing "special" about the operator[] method, except that its name must be exactly as written in order to communicate to the compiler that this method is, in fact, to be treated as an overloading of the built-in "[]" operator for objects of the Array class.

Two safe arrays may be added or subtracted by overloading the "+" operator and the "-" operator. The interface of the Array class would then be changed as follows:

   class Array
    private:
      int array[20];
    public:
             Array();
      int&   operator[](int i);		// subscript   operator
      Array& operator+(Array& other);	// addition    operator
      Array& operator-(Array& other);	// subtraction operator
            ~Array();
   };
   
Notice that the addition and subtraction operators return a reference to an Array object that holds the result of the addition. The two Array objects being added are not changed. Instead a new Array object is created and returned by reference.

The Array addition operator allows the following usages:


     Array a,b;		// initialized to 0
     Array one(1);	// initialized to 1

     // give values to arrays a and b

     Array& c = a + b;
     Array& d = a - b + one;

     
The first of the two assignment statements produces an array each of whose elements is the sum of the corresponding elements in the two arrays "a" and "b". The second assignment shows that the overloaded addition and substraction operators for the Array class can be used in more complicated expressions.

The compiler will, on encountering the expression "a + b" determine if there is a class specific overloading of the addition operator. Since "a" and "b" are objects of the Array class and this class contains an "operator+" method with matching argument types, the compiler will generate code that is the equivalent of:

	a.operator+(b)
where the object ("a") on the left-hand side of the addition in the expression "a +"b plays the role of the called object and the object ("b") on the right hand side plays the role of the argument value. Similarly, the statement:
	d = a - b + one
will be compiled into code that is equivalent to:
	Array& anonymous = a.operator-(b);
        d =anonymous.operator+(one);
where the name "anonymous" is used here to refer to the object created dynamically by the subtraction operator.

The use of the default assignment operator in the Array class is sufficient. The default assignment operator simply performs a bit-level copy from the source to the target object. In this case, assignments such as:


	Array f, g;

        // assign values to g

	f = g;
will work as intended: the data in Array g will be copied to the data in Array f.

The implementation of the Array addition and subtraction operators is as follows:

    Array& Array::operator+(Array& other) {
      Array *c = new Array;
      Array& result = *c;

      for(int i = 0; i< 20; i++) result[i] = array[i] + other.array[i];

      return result;
    }

    Array& Array::operator-(Array& other) {
      Array *c = new Array;
      Array& result = *c;

      for(int i = 0; i< 20; i++) result[i] = array[i] - other.array[i];

      return result;
    }
Each operation allocates a new Array object that it will return by reference as its result. Because the variable "c" is a pointer to an Array object (not an Array object itself), it is not possible to write "c[i]". Using the variable "c" directly it would be necessary to write (*c)[i] instead. While this usage is correct, it is awkward to always remember to first dereference the pointer before applying the subscript operator. Also the parenthesis are necessary to insure that the dereference happens first, writing "*c[i]" is incorrect. Instead, the variable "result" is defined as a reference to the newly allocated Array. Since result is a reference to an Array it is possible to write "result[i]".

Exercises

  1. Extend the Array class to include an operator that would allow each element of the array to be multiplied by a given constant. For example:
            Array  a;
            Array& b = a*6;  // multiple each element by 6
         

  2. Extend the Array class to include a test for equality operator that returns 1 if all elements of the two Array objects are the same and 0 otherwise. For example:
            Array  a;
            Array  b;
    
            // give values to a and b
    
            if ( a == b) {// uses equality operator
                          // code for when they are equal
                         }
         

Last Updated: November 9, 1995 / kafura@cs.vt.edu