Operator Overloading

Steven Zeil

Last modified: Jun 11, 2020
Contents:

1 Operators

Lots of Operators

In C++, almost every operator in the language can be declared for our own classes.

1.1 Operators are Functions

Examples: operators as functions

Declaring Operators

Understanding that shorthand, we can declare our own operators by giving the appropriate operator function name:

class BidCollection {
      ⋮
  void addInTimeOrder (const Bid& value);
  //  Adds this bid into a position such that 
  //   all bids are ordered by the time the bid was placed
  //Pre: getSize() < getMaxSize()

  void operator+= (const Bid& value)  
           {addInTimeOrder(value);}

Then

bids.addInTimeOrder(b);

and

bids += b;

would do the same thing

Keep it Natural

Most Commonly Overloaded Operators

There are 3 groups of operators that are very commonly overloaded

2 I/O Operators

Perhaps the most commonly declared operators

Providing the Output Operator

For example, our Bid class already looks like:

class Bid {
  ⋮
public:
  Bid (std::string bidder, double amt, 
       std::string item, Time placedAt);

  Bid();

  // Access to attribute
  std::string getBidder() const {return bidderName;}
  double getAmount() const {return amount;}
  std::string getItem() const {return itemName;}
  Time  getTimePlacedAt() const {return bidPlacedAt;}

  // Print a bid
  void print (std::ostream& out) const;
};

All we need to do is add:

inline
std::ostream& operator<< (std::ostream& out, const Bid& b)
{
  b.print(out);
  return out;
}

Lots of Ops

The whole program, with output operators:

Debugging with the Output Op

Note how much cleaner the debugging output statements look.

Instead of

  if (bid.getAmount() <= bidder.getBalance())
    {
      highestBidSoFar = bid.getAmount();
      winningBidderSoFar = bid.getBidder();
      cerr << "Best bid so far is ";
      bid.print (cerr);
      cerr << " from ";
      bidder.print (cerr);
      cerr << endl;
    }

we now can write

  if (bid.getAmount() <= bidder.getBalance())
    {
      highestBidSoFar = bid.getAmount();
      winningBidderSoFar = bid.getBidder();
      cerr << "Best bid so far is "
           << bid << " from "
           << bidder << endl;
    }

Return Values

The << operator must return the stream it is applied to.

Fine Points

3 Comparison Operators

Almost every class should provide == and < operators.

../opoverload-auctionComp/arrayUtils.h
#ifndef ARRAYUTILS_H
#define ARRAYUTILS_H



//  Add to the end
//  - Assumes that we have a separate integer (size) indicating how
//     many elements are in the array
//  - and that the "true" size of the array is at least one larger 
//      than the current value of that counter
template <typename T>
void addToEnd (T* array, int& size, T value)
{
   array[size] = value;
   ++size;
}



// Add value into array[index], shifting all elements already in positions
//    index..size-1 up one, to make room.
//  - Assumes that we have a separate integer (size) indicating how
//     many elements are in the array
//  - and that the "true" size of the array is at least one larger 
//      than the current value of that counter

template <typename T>
void addElement (T* array, int& size, int index, T value)
{
  // Make room for the insertion
  int toBeMoved = size - 1;
  while (toBeMoved >= index) {
    array[toBeMoved+1] = array[toBeMoved];
    --toBeMoved;
  }
  // Insert the new value
  array[index] = value;
  ++size;
}


// Assume the elements of the array are already in order
// Find the position where value could be added to keep
//    everything in order, and insert it there.
// Return the position where it was inserted
//  - Assumes that we have a separate integer (size) indicating how
//     many elements are in the array
//  - and that the "true" size of the array is at least one larger 
//      than the current value of that counter

template <typename T>
int addInOrder (T* array, int& size, T value)
{
  // Make room for the insertion
  int toBeMoved = size - 1;
  while (toBeMoved >= 0 && value < array[toBeMoved]) {
    array[toBeMoved+1] = array[toBeMoved];
    --toBeMoved;
  }
  // Insert the new value
  array[toBeMoved+1] = value;
  ++size;
  return toBeMoved+1;
}


// Search an array for a given value, returning the index where 
//    found or -1 if not found.
template <typename T>
int seqSearch(const T list[], int listLength, T searchItem)
{
    int loc;

    for (loc = 0; loc < listLength; loc++)
        if (list[loc] == searchItem)
            return loc;

    return -1;
}


// Search an ordered array for a given value, returning the index where 
//    found or -1 if not found.
template <typename T>
int seqOrderedSearch(const T list[], int listLength, T searchItem)
{
    int loc = 0;

    while (loc < listLength && list[loc] < searchItem)
      {
       ++loc;
      }
    if (loc < listLength && list[loc] == searchItem)
       return loc;
    else
       return -1;
}


// Removes an element from the indicated position in the array, moving
// all elements in higher positions down one to fill in the gap.
template <typename T>
void removeElement (T* array, int& size, int index)
{
  int toBeMoved = index + 1;
  while (toBeMoved < size) {
    array[toBeMoved] = array[toBeMoved+1];
    ++toBeMoved;
  }
  --size;
}



// Search an ordered array for a given value, returning the index where 
//    found or -1 if not found.
template <typename T>
int binarySearch(const T list[], int listLength, T searchItem)
{
    int first = 0;
    int last = listLength - 1;
    int mid;

    bool found = false;

    while (first <= last && !found)
    {
        mid = (first + last) / 2;

        if (list[mid] == searchItem)
            found = true;
        else 
            if (searchItem < list[mid])
                last = mid - 1;
            else
                first = mid + 1;
    }

    if (found) 
        return mid;
    else
        return -1;
}





#endif

Comparison Operators for Time

time.h
#ifndef TIMES_H
#define TIMES_H

#include <iostream>

/**
 * Times in this program are represented by three integers: H, M, & S, representing
 * the hours, minutes, and seconds, respecitvely.
 */


class Time {
public:
  // Create time objects
  Time(); // midnight
  Time (int h, int m, int s);

  // Access to attributes
  int getHours() const;
  int getMinutes() const;
  int getSeconds() const;

  // Calculations with time
  void add (Time delta);
  Time difference (Time fromTime);
  
  /**
   * Read a time (in the format HH:MM:SS) after skipping any
   * prior whitepace.
   */
  void read (std::istream& in);


  /**
   * Print a time in the format HH:MM:SS (two digits each)
   */
  void print (std::ostream& out) const;



  // Comparison operators
  bool operator== (const Time&) const;
  bool operator< (const Time&) const;

private:
  // From here on is hidden
  int secondsSinceMidnight;
};

inline
std::ostream& operator<< (std::ostream& out, const Time& t)
{
  t.print(out);
  return out;
}


inline
bool operator!= (const Time& t1, const Time& t2)
{
  return !(t1 == t2);
}

inline
bool operator> (const Time& t1, const Time& t2)
{
  return t2 < t1;
}

inline
bool operator<= (const Time& t1, const Time& t2)
{
  return !(t1 > t2);
}

inline
bool operator>= (const Time& t1, const Time& t2)
{
  return !(t1 < t2);
}

#endif // TIMES_H
time.cpp
#include "time.h"
#include <iomanip>

using namespace std;

/**
 * Times in this program are represented by three integers: H, M, & S, representing
 * the hours, minutes, and seconds, respecitvely.
 */



  // Create time objects
Time::Time() // midnight
{
  secondsSinceMidnight = 0;
}


Time::Time (int h, int m, int s)
{
  secondsSinceMidnight = s + 60 * m + 3600*h;
}

  // Access to attributes
int Time::getHours() const
{
  return secondsSinceMidnight / 3600;
}

int Time::getMinutes() const
{
  return (secondsSinceMidnight % 3600) / 60;
}

int Time::getSeconds() const
{
  return secondsSinceMidnight % 60;
}

// Calculations with time
void Time::add (Time delta)
{
  secondsSinceMidnight += delta.secondsSinceMidnight;
}
      

Time Time::difference (Time fromTime)
{
  Time diff;
  diff.secondsSinceMidnight = 
    secondsSinceMidnight - fromTime.secondsSinceMidnight;
}
  
  
/**
 * Read a time (in the format HH:MM:SS) after skipping any
 * prior whitepace.
 */
void Time::read (std::istream& in)
{
  char c;
  int hours, minutes, seconds;
  in >> hours >> c >> minutes >> c >> seconds;
  Time t (hours, minutes, seconds);
  secondsSinceMidnight = t.secondsSinceMidnight;
}


/**
 * Print a time in the format HH:MM:SS (two digits each)
 */
void Time::print (std::ostream& out) const
{
  out << setfill('0') << setw(2) << getHours() << ':' 
      << setfill('0') << setw(2) << getMinutes() << ':' 
      << setfill('0') << setw(2) << getSeconds();
}



// Comparison operators
bool Time::operator< (const Time& time2) const
{
  return secondsSinceMidnight < time2.secondsSinceMidnight;
}

bool Time::operator==(const Time& time2) const
{
  return secondsSinceMidnight == time2.secondsSinceMidnight;
}

3.1 Equality Operators

Equality Operators for Compound Data

Common: Compare Every Member

For ADTs with multiple data members, we often expect every “significant” data member to match. E.g.,

class Bid {

  std::string bidderName;
  double amount;
  std::string itemName;
  Time bidPlacedAt;
    ⋮

can be compared like this

bool Bid::operator== (const Bid& b) const
{
  return bidderName == b.bidderName
    && amount == b.amount
    && itemName == b.itemName
    && bidPlacedAt == b.bidPlacedAt;
}

Highly Variable Data

Fields that change frequently may or may not be significant:

bool Bidder::operator== (const Bidder& b) const
{
  return name == b.name;  // ignore balance
}

Equality for ADTs with Arrays

bool BidCollection::operator== (const BidCollection& bc) const
{
  if (size == bc.size)                  ➀
    {
      for (int i = 0; i < size; ++i)         ➁
        if (!(elements[i] == ➂bc.elements[i])) 
          return false;
      return true;
    }
  else
    return false;
}

3.2 Less-Than Operators

< is not always “less than”

Less-than on Compound Data

Extending Lexicographic Order to Non-Strings

Compare members until we find two that are not equal:

bool Bid::operator< (const Bid& b) const
{
  if (bidPlacedAt < b.bidPlacedAt)
    return true;
  else if (b.bidPlacedAt < bidPlacedAt)
    return false;

  if (bidderName < b.bidderName)
    return true;
  else if (b.bidderName < bidderName)
    return false;

  if (itemName < b.itemName)
    return true;
  else if (b.itemName < itemName)
    return false;

  if (amount < b.amount)
    return true;
  else if (b.amount < amount)
    return false;

  return false;
}

Less-than for ADTs with Arrays

We extend the same idea to arrays of data:

bool BidCollection::operator< (const BidCollection& bc) const
{
  if (size == bc.size)                 ➀
    {
      for (int i = 0; i < size; ++i)   ➁
        {
          if (elements[i] < bc.elements[i])   ➂
            return true;
          else if (bc.elements[i] < elements[i])  ➂
            return false;
          // else keep going
        }

      // All of the elements are equal
      return false;                               ➃
    }
  else
    return size < bc.size;   ➀
}

Adding Both == and <

We often have choices in how to implement these two.

Why Just Two?

timeComp.h
#ifndef TIMES_H
#define TIMES_H

#include <iostream>

/**
 * Times in this program are represented by three integers: H, M, & S, representing
 * the hours, minutes, and seconds, respecitvely.
 */


class Time {
public:
  // Create time objects
  Time(); // midnight
  Time (int h, int m, int s);

  // Access to attributes
  int getHours() const;
  int getMinutes() const;
  int getSeconds() const;

  // Calculations with time
  void add (Time delta);
  Time difference (Time fromTime);
  
  /**
   * Read a time (in the format HH:MM:SS) after skipping any
   * prior whitepace.
   */
  void read (std::istream& in);


  /**
   * Print a time in the format HH:MM:SS (two digits each)
   */
  void print (std::ostream& out) const;



  // Comparison operators
  bool operator== (const Time&) const;
  bool operator< (const Time&) const;

private:
  // From here on is hidden
  int secondsSinceMidnight;
};

inline
std::ostream& operator<< (std::ostream& out, const Time& t)
{
  t.print(out);
  return out;
}


inline
bool operator!= (const Time& t1, const Time& t2)
{
  return !(t1 == t2);
}

inline
bool operator> (const Time& t1, const Time& t2)
{
  return t2 < t1;
}

inline
bool operator<= (const Time& t1, const Time& t2)
{
  return !(t1 > t2);
}

inline
bool operator>= (const Time& t1, const Time& t2)
{
  return !(t1 < t2);
}

#endif // TIMES_H

Taking Advantage of the Relational Ops

If we make a habit of providing these, we can often use “canned” versions of our algorithms instead of needing to write specialized versions.

For example, given:

../opoverload-auctionComp/arrayUtils.h
#ifndef ARRAYUTILS_H
#define ARRAYUTILS_H



//  Add to the end
//  - Assumes that we have a separate integer (size) indicating how
//     many elements are in the array
//  - and that the "true" size of the array is at least one larger 
//      than the current value of that counter
template <typename T>
void addToEnd (T* array, int& size, T value)
{
   array[size] = value;
   ++size;
}



// Add value into array[index], shifting all elements already in positions
//    index..size-1 up one, to make room.
//  - Assumes that we have a separate integer (size) indicating how
//     many elements are in the array
//  - and that the "true" size of the array is at least one larger 
//      than the current value of that counter

template <typename T>
void addElement (T* array, int& size, int index, T value)
{
  // Make room for the insertion
  int toBeMoved = size - 1;
  while (toBeMoved >= index) {
    array[toBeMoved+1] = array[toBeMoved];
    --toBeMoved;
  }
  // Insert the new value
  array[index] = value;
  ++size;
}


// Assume the elements of the array are already in order
// Find the position where value could be added to keep
//    everything in order, and insert it there.
// Return the position where it was inserted
//  - Assumes that we have a separate integer (size) indicating how
//     many elements are in the array
//  - and that the "true" size of the array is at least one larger 
//      than the current value of that counter

template <typename T>
int addInOrder (T* array, int& size, T value)
{
  // Make room for the insertion
  int toBeMoved = size - 1;
  while (toBeMoved >= 0 && value < array[toBeMoved]) {
    array[toBeMoved+1] = array[toBeMoved];
    --toBeMoved;
  }
  // Insert the new value
  array[toBeMoved+1] = value;
  ++size;
  return toBeMoved+1;
}


// Search an array for a given value, returning the index where 
//    found or -1 if not found.
template <typename T>
int seqSearch(const T list[], int listLength, T searchItem)
{
    int loc;

    for (loc = 0; loc < listLength; loc++)
        if (list[loc] == searchItem)
            return loc;

    return -1;
}


// Search an ordered array for a given value, returning the index where 
//    found or -1 if not found.
template <typename T>
int seqOrderedSearch(const T list[], int listLength, T searchItem)
{
    int loc = 0;

    while (loc < listLength && list[loc] < searchItem)
      {
       ++loc;
      }
    if (loc < listLength && list[loc] == searchItem)
       return loc;
    else
       return -1;
}


// Removes an element from the indicated position in the array, moving
// all elements in higher positions down one to fill in the gap.
template <typename T>
void removeElement (T* array, int& size, int index)
{
  int toBeMoved = index + 1;
  while (toBeMoved < size) {
    array[toBeMoved] = array[toBeMoved+1];
    ++toBeMoved;
  }
  --size;
}



// Search an ordered array for a given value, returning the index where 
//    found or -1 if not found.
template <typename T>
int binarySearch(const T list[], int listLength, T searchItem)
{
    int first = 0;
    int last = listLength - 1;
    int mid;

    bool found = false;

    while (first <= last && !found)
    {
        mid = (first + last) / 2;

        if (list[mid] == searchItem)
            found = true;
        else 
            if (searchItem < list[mid])
                last = mid - 1;
            else
                first = mid + 1;
    }

    if (found) 
        return mid;
    else
        return -1;
}





#endif