Operator Overloading

Operator Overloading

(Ref. Lippman 15.1-15.7, 15.9)

We have already seen how functions can be overloaded in C++. We can also overload operators, such as the + operator, for classes that we write. Note that operators for built-in types may not be created or modified. A complete list of overloadable operators can be found in Lippman, Table 15.1.

A Complex Number Class

In this example, we have overloaded the following operators: +, *, >, =, (), [] and the cast operator.

operator+() and operator*()

Let a and b be of type Complex. The + operator in the expression a+b, can be interpreted as

    a.operator+(b)

where operator+() is a member function of class Complex. We then have to write this function so that it adds the real and imaginary parts of a and b and returns the result as a new Complex object. Note that the function returns by value, since it has to create a temporary object for the result of the addition.

Our implementation will also work for an expression like a+b+c. In this case, the operator will be invoked in the following order

    (a.operator+(b)).operator+(c)

The member operator+() will also work for an expression like

    a + 7.0

because we have a constructor that can create a Complex from a single double. However, the expression

    7.0 + a

will not work, because operator+() is a member of class Complex, and not the built-in double type.

To solve this problem, we can make the operator a global function, as we have done with operator*(). The expression 7.0 * a will be interpreted as

    operator*(7.0, a)

Since a global function does not automatically have access to the private data of class Complex, we can grant special access to operator*() by making it a friend function of class Complex. In general, friend functions and classes should be used sparingly, since they are a violation of the rule of encapsulation.

operator>()

We have overloaded this operator to allow comparison of two Complex numbers, based on their magnitudes.

operator=()

The = operator is designed to work with statements like

    a = b;
    a = b = c;

These statements respectively interpreted as

    a.operator=(b);
    a.operator=(b.operator=(c));

In this case, the operator changes the object that invokes it, and it returns a reference to this object so that the second statement will work. The keyword this gives us a pointer to the current object within the class definition.

operator Point()

This operator allows us to convert a Complex object to a Point object. It can be used in an explicit cast, like

    Complex a;
    Point p;
    p = (Point)a;

but it could also be invoked implicitly, as in

    p = a;

Hence, a great deal of caution should be used when providing user-defined type conversions in this way. An alternative way to convert from a Complex to a Point is to give the Point class a constructor that takes a Complex as an argument. However, we might not have access to the source code for the Point class, if it were written by someone else.

Note that overloaded cast operators do not have a return type.

operator()()

This is the function call operator. It is invoked by a Complex object a as

    a()

Here we have overloaded operator()() to return true if the object has an imaginary part and false otherwise.

operator[]()

The overloaded subscript operator is useful if we wish to access fields of the object like array elements. In our example, we have made a[0] refer to the real part and a[1] refer to the imaginary part of the object.

operator<<()

The output operator cannot be overloaded as a member function because we do not have access to the ostream class to which the predefined object cout belongs. Instead, operator<<() is overloaded as a global function. We must return a reference to the ostream class so that the output operator can be concatenated. For example,

    cout << a << b;

will be invoked as

    operator<<((operator<<(cout, a),b)
 

complex.h

// Interface for class Complex.
#ifndef _COMPLEX_H_
#define _COMPLEX_H_

#include <iostream.h>
#include "point.h"

#ifndef DEBUG_PRINT
#ifdef _DEBUG
#define DEBUG_PRINT(str)  cout << str << endl;
#else
#define DEBUG_PRINT(str)
#endif
#endif

class Complex {
        private:
    double mdReal;
    double mdImag;

        public:
    // This combines three constructors in one. Here we have used an initialization list to initialize
    // the private data, instead of using assignment statements in the body of the constructor.
    Complex(double dReal=0.0, double dImag=0.0) : mdReal(dReal), mdImag(dImag) {
        DEBUG_PRINT("In Complex::Complex(double dReal, double dImag)")
    }

    // The copy constructor.
    Complex(const Complex& c);
    ~Complex() {
        DEBUG_PRINT("In Complex::~Complex()")
    }
    void print();

    // Overloaded member operators.
    Complex operator+(const Complex& c) const;      // Overloaded + operator.
    int operator>(const Complex& c) const;                // Overloaded > operator.
    Complex& operator=(const Complex& c);           // Overloaded = operator.
    operator Point() const;                  // Overloaded cast-to-Point operator.
    bool operator()(void) const;          // Overloaded function call operator.
    double& operator[](int i);              // Overloaded subscript operator.

    // Overloaded global operators. We make these operators friends of class
    // Complex, so that they will have direct access to the private data.
    friend ostream& operator<<(ostream& os, const Complex& c);       // Overloaded output operator.
    friend Complex operator*(const Complex& c, const Complex& d);   // Overloaded * operator.
};
#endif    // _COMPLEX_H_
 

complex.C

// Implementation for class Complex.
#include "complex.h"
#include <stdlib.h>

// Definition of copy constructor.
Complex::Complex(const Complex& c) {
    DEBUG_PRINT("In Complex::Complex(const Complex& c)")
    mdReal = c.mdReal;
    mdImag = c.mdImag;
}

// Definition of overloaded + operator.
Complex Complex::operator+(const Complex& c) const {
    DEBUG_PRINT("In Complex Complex::operator+(const Complex& c) const")
    return Complex(mdReal + c.mdReal, mdImag + c.mdImag);
}
 

// Definition of overloaded > operator.
int Complex::operator>(const Complex& c) const {
    double sqr1 = mdReal * mdReal + mdImag * mdImag;
    double sqr2 = c.mdReal * c.mdReal + c.mdImag * c.mdImag;

    DEBUG_PRINT("In int Complex::operator>(const Complex& c) const")
    return (sqr1 > sqr2);
}
 

// Definition of overloaded assignment operator.
Complex& Complex::operator=(const Complex& c) {
    DEBUG_PRINT("In Complex& Complex::operator=(const Complex& c)")
    mdReal = c.mdReal;
    mdImag = c.mdImag;
    return *this;
}
 

// Definition of overloaded cast-to-Point operator. This converts a Complex object to a Point object.
Complex::operator Point() const {
    float fX, fY;

    DEBUG_PRINT("In Complex::operator Point() const")
    // Our Point class uses floats instead of doubles. In this case, we make a conscious decision
    // to accept a loss in precision by converting the doubles to floats. Be careful when doing this!
    fX = (float)mdReal;
    fY = (float)mdImag;

    return Point(fX, fY);
}

// Definition of overloaded function call operator. We have defined this operator to allow us to test
// whether a number is complex or real.
bool Complex::operator()(void) const {
    DEBUG_PRINT("In bool Complex::operator()(void) const")
    if (mdImag == 0.0)
        return false;     // Number is real.
    else
        return true;      // Number is complex.
}

// Definition of overloaded subscript operator. We have defined this operator to allow access to
// the real and imaginary parts of the object.
double& Complex::operator[](int i) {
    DEBUG_PRINT("In double& Complex::operator[](int)")
    switch(i) {
            case 0:
        return mdReal;

            case 1:
        return mdImag;

            default:
        cerr << "Index out of bounds" << endl;
        exit(0);                           // A function in the C standard library.
    }
}

// Definition of a print function.
void Complex::print() {
    cout << mdReal << " + j" << mdImag << endl;
}

// Definition of overloaded output operator. Note that this is a global function. We can
// access the private data of the Complex object c because the operator is a friend function.
ostream& operator<<(ostream& os, const Complex& c) {
    DEBUG_PRINT("In ostream& operator<<(ostream&, const Complex&)")
    cout << c.mdReal << " + j" << c.mdImag;
    return os;
}

// Definition of overloaded * operator. By making this operator a global function, we can
// handle statements such as a = 7 * b, where a and b are Complex objects.
Complex operator*(const Complex& c, const Complex& d) {
    DEBUG_PRINT("In Complex operator*(const Complex& c, const Complex& d)")
    double dReal = c.mdReal*d.mdReal - c.mdImag*d.mdImag;
    double dImag = c.mdReal*d.mdImag + c.mdImag*d.mdReal;
    return Complex(dReal, dImag);
}
 

complex_test.C

#include "complex.h"

void main() {
    Complex a;
    Complex b;
    Complex *c;
    Complex d;

    // Use of constructors and the overloaded operator=().
    a = (Complex)2.0;                 // Same as a = Complex(2.0);
    b = Complex(3.0, 4.0);
    c = new Complex(5.0, 6.0);

    // Use of the overloaded operator+().
    d = a + b;                              // Same as d = a.operator+(b);
    d.print();

    d = a + b + *c;                      // Same as d = (a.operator+(b)).operator+(*c);
    d.print();

    // Use of the overloaded operator>().
    if (b > a)
        cout << "b > a" << endl;
    else
        cout << "b <= a" << endl;

    // Use of cast-to-Point operator. This will convert a Complex object to a Point object.
    // An alternative way to handle the type conversion is to give the Point class a constructor
    // that takes a Complex object as an argument.
    Point p;
    p = (Point)b;
    p.print();

    // Use of the overloaded operator()().
    if (a() == true)
        cout << "a is a complex number" << endl;
    else
        cout << "a is a real number" << endl;

    // Use of the overloaded operator[](). This will change the imaginary part of a.
    a[1] = 8.0;
    a.print();

    // Use of the overloaded global operator<<().
    cout << "a = " << a << endl;

    // User of the overloaded global operator*(). The double literal constant will be passed as the
    // first argument to operator*() and it will be converted to a Complex object using the Complex
    // constructor.  This statement would not be legal if the operator were a member function.
    d = 7.0 * b;

    cout << "d = " << d << endl;
}