In C++, you can overload most of the operators in the language
(like +, -, *, [],
and even weird ones like (), new and
delete) with customized code.
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];
}
[]. 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.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;
}
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;
}
}