CS105: Introduction to Computer Programming: C++

C++: Operator Overloading

In C++, you can overload most of the operators in the language (like +, -, *, [], and even weird ones like (), new and delete) with customized code.

Overloading operators with class member functions

Let's say we made an Array class. Wouldn't it be cool if we could treat an instance of our class just like we treat normal arrays?

  Array arr;
  arr[0] = 20;  // use the [] operator to get the 0-th element of our array!
  cout << "arr[0] is " << arr[0] << endl;

We can give our class this functionality by overloading the [] operator with a class member function.

Array.h
/**
 * A simple Array class with bounds checking.
 */
class Array {
public:

  /**
   * Construct a new array of size n.  If n is not provided, then
   * it defaults to 10.
   */
  Array( int n = 10 );
  ~Array();

  /**
   * Return the idx-th element of this array.  We'll define two 
   * versions: one when we want to just access elements of our array,
   * and one for when we want to assign values to our array.
   */
  const int operator[]( int idx ) const;
  int& operator[]( int idx );

private:

  // our dynamically allocated array of data
  int* data;
  // the size of our array
  int size;
};
Array.cc
#include <iostream>
using std::cerr;
using std::endl;
#include "Array.h"


Array::Array( int n ) {
  size = n;
  data = new int[n];
}

Array::~Array() {
  delete[] data;
}

const int Array::operator[]( int idx ) const {
  if( idx < 0 || idx >= size ) {
    cerr << "Error: index " << idx << " not between 0 and " << size << endl;
    exit(-1);
  }

  return data[idx];
}

int& Array::operator[]( int idx ) {
  if( idx < 0 || idx >= size ) {
    cerr << "Error: index " << idx << " not between 0 and " << size << endl;
    exit(-1);
  }

  return data[idx];
}
Note that we define two versions of our array access operator []. The compiler will use the const version of the operator whenever we use the operator in a situation where the array shouldn't be changed, like cout << arr[0]. When we use the operator in an assignment context (like arr[0] = 5;) the second version of the operator is used. Only the second version allows the programmer to change the contents of the array.

Overloading operators with global functions

Sometimes you don't have access to the class that you'd like to define an overloaded operator for. For example, what if we wanted to be able to display the Array class above using the << operator?

  Array arr(5);
  cout << arr;

We can't accomplish this by adding a function called operator<< to cout's class, because cout belongs to a pre-compiled library somewhere that we don't have access to.

All is not lost, however. If the compiler can't find a member function of cout to do the job, it will look for a normal non-class function like this:

  ostream& operator<<( ostream& os, const Array& arr ) {
    ...
  }

This function should be defined outside of the Array class. However, if you're worried about efficiency, you can make this function a friend of the Array class. Functions that are friends of a class can access private data of that class.

Here is a fancier version of our Array class that has several different overloaded operators:

Array.h
#ifndef ARRAY_H
#define ARRAY_H

#include <ostream>
using std::ostream;
#include <istream>
using std::istream;


class Array {

  /**
   * Overloaded Array operators, implemented as global "friend"
   * functions.  These are not actually part of the Array class, but
   * they can access private data of the Array class.
   */
  friend ostream& operator<<( ostream& os, const Array& arr );
  friend istream& operator>>( istream& is, Array& arr );
  friend Array operator+( const Array& arr1, const Array& arr2 );

public:

  /**
   * Construct a new array of size N
   */
  Array( int n = 10 );
  ~Array();

  /**
   * Return the idx-th element of this array.  R-value and L-value
   * versions!
   */
  const int operator[]( int idx ) const;
  int& operator[]( int idx );

  /**
   * Overloaded equality testers
   */
  bool operator==( const Array& rhs ) const;
  bool operator!=( const Array& rhs ) const;

  /**
   * The ! operator - return true if the array has no elements or if
   * it's all zeros.
   */
  bool operator!() const;

  /**
   * An overloaded cast operator.  When we cast an Array to a bool,
   * this operator will be used.  Does the opposite of operator!().
   */
  operator bool() const;

private:

  int* data;
  int size;

};

#endif
Array.cc
#include <iostream>
using std::cerr;
using std::endl;
#include "Array.h"


Array::Array( int n ) {
  size = n;
  data = new int[n];
}



Array::~Array() {
  delete[] data;
}



const int Array::operator[]( int idx ) const {
  if( idx < 0 || idx >= size ) {
    cerr << "Error: index " << idx << " not between 0 and " << size << endl;
    exit(-1);
  }

  return data[idx];
}



int& Array::operator[]( int idx ) {
  if( idx < 0 || idx >= size ) {
    cerr << "Error: index " << idx << " not between 0 and " << size << endl;
    exit(-1);
  }

  return data[idx];
}



bool Array::operator==( const Array& rhs ) const {
  if( size != rhs.size ) return false;

  for( int i = 0; i < size; i++ ) {
    if( data[i] != rhs.data[i] ) return false;
  }

  return true;
}



bool Array::operator!=( const Array& rhs ) const {
  return !(*this == rhs);
}



bool Array::operator!() const {
  if( size == 0 ) return true;
  for( int i = 0; i < size; i++ ) {
    if( data[i] != 0 ) return false;
  }
  return true;
}



Array::operator bool() const {
  return !this->operator!();
}



ostream& operator<<( ostream& os, const Array& arr ) {
  os << "[";
  for( int i = 0; i < arr.size; i++ ) {
    os << arr.data[i];
    if( i < arr.size-1 ) os << " ";
  }
  os << "]";
  return os;
}



istream& operator>>( istream& is, Array& arr ) {
  for( int i = 0; i < arr.size; i++ ) {
    is >> arr.data[i];
  }
  return is;
}



Array operator+( const Array& arr1, const Array& arr2 ) {
  int resultSize = arr1.size + arr2.size;
  Array result(resultSize);
  
  int counter = 0;
  for( int i = 0; i < arr1.size; i++ ) result[counter++] = arr1[i];
  for( int i = 0; i < arr2.size; i++ ) result[counter++] = arr2[i];

  return result;
}

Date class example

Here is an example of overloaded operators in a Date class:

Date.h
#ifndef DATE_H
#define DATE_H

#include <iostream>
using std::ostream;

class Date {

  friend ostream& operator<<( ostream& os, const Date& rhs );

public:
  
  Date( int d );
  Date( int d, int m, int y );

  void print() const;

  Date& operator++();  // pre
  Date operator++( int dummy );  // post
  Date operator+( int numDays ) const; 


  int getDay() const { return day; }
  int getMonth() const { return month; }
  int getYear() const { return year; }

private:

  static int getMaxDays( int month );

  int day, month, year;
};


Date operator+( int numDays, const Date& rhs );


#endif
Date.cc
#include <iostream>
using std::cout;
#include "Date.h"


ostream& operator<<( ostream& os, const Date& rhs ) {
  os << rhs.day << "-" 
     << rhs.month << "-"
     << rhs.year;
  return os;
}


Date operator+( int numDays, const Date& rhs ) {
  return rhs + numDays;
}


Date::Date( int d ) : 
  day(d), month(0), year(0) {
  // nothing
}



Date::Date( int d, int m, int y ) :
  day(d), month(m), year(y) {
  // nothing
}



void Date::print() const {
  cout << day << "-" << month << "-" << year << '\n';
}


Date& Date::operator++() {            // pre-inc
  int maxDays = getMaxDays(month);
  ++day;
  if( day > maxDays ) {
    day = 1;
    ++month;
    if( month > 12 ) {
      ++year;
      month = 1;
    }
  }
  return *this;
}


Date Date::operator++( int dummy ) {  // post-inc
  Date result = *this;
  ++(*this);
  return result;
}



Date Date::operator+( int numDays ) const {
  Date result = *this;

  for( int i = 0; i < numDays; ++i ) {
    ++result;
  }

  return result;
}




int Date::getMaxDays( int month ) {
  switch( month ) {
  case 2:
    return 28; // leap year bug
  case 1:
  case 3:
  case 5:
  case 7:
  case 8:
  case 10:
  case 12:
    return 31;
  default:
    return 30;
  }
}