Operator Overloading in C++: A Comprehensive Guide

Chapter 14: Operator Overloading in C++

Chapter Objectives

After completing this chapter, you should be able to:

  • Understand the concept of operator overloading.

The Need for Operator Overloading

Let’s revisit the clockType class from Chapter 11. Consider the following code snippet:

clockType noon(12, 0, 0);clockType classTime(11, 30, 0);noon.printTime();classTime.incrementSeconds();if (noon.equalTime(classTime)) {    cout << "The times are equal" << endl;}

These statements work as intended, but C++ offers more intuitive ways to perform these operations. For instance, we typically use:

  • ++ to increment a value.
  • == to compare two values for equality.

Operator overloading allows us to define how these standard C++ operators behave with objects of user-defined classes like clockType.

Understanding Operator Overloading

Many operators in C++ are already overloaded to work with various built-in data types. Take the output operator (<<) as an example. The following output statements are all valid:

cout << 10;cout << 3.14;cout << "Hello";cout << 'A';

The compiler achieves this through operator functions. The keyword operator precedes the operator symbol to form the function name. Here are some examples:

OperatorFunction Name
<<operator<<
++operator++
==operator==
+operator+

Recall that overloading means having multiple functions with the same name. Since the output operator is overloaded, there are multiple operator<< functions. The compiler determines which function to call based on the parameter list. For example:

  1. void operator<<(ostream&, int);
  2. void operator<<(ostream&, double);
  3. void operator<<(ostream&, const char*);
  4. void operator<<(ostream&, char);

When the compiler encounters an operator, it calls the corresponding function, passing the left and right operands as parameters.

cout << 10; // Calls function declaration 1cout << 3.14; // Calls function declaration 2cout << "Hello"; // Calls function declaration 3cout << 'A'; // Calls function declaration 4

Restrictions on Operator Overloading

There are some limitations to operator overloading:

  • You cannot alter the precedence of an operator.
  • The associativity (left-to-right or right-to-left evaluation) cannot be changed.
  • Default parameters are not allowed.
  • You cannot modify the number of parameters an operator accepts.
  • You cannot introduce new operators.
  • Certain operators cannot be overloaded: ., .*, ::, ?:, sizeof.
  • The fundamental meaning of an operator should be preserved (e.g., + should represent addition).

Overloading Binary Operators as Non-Member Functions

Binary operators are typically overloaded as non-member functions, meaning they are not part of the class itself. We define them outside the class declaration.

Overloading the Output Operator (<<)

Let’s demonstrate this with the clockType class. We’ll overload the output operator to display the time in the format HH:MM:SS.

// clockTypeA.h#include <iostream>using namespace std;class clockType {public:    clockType();    clockType(int, int, int);    void setTime(int, int, int);    void getTime(int&, int&, int&);    void incrementSeconds();    void incrementMinutes();    void incrementHours();    bool equalTime(const clockType& otherClock) const;private:    int hr;    int min;    int sec;};ostream& operator<<(ostream&, const clockType&); // Non-member function declaration
// clockTypeA.cpp#include <iostream>#include "clockTypeA.h"using namespace std;// ... (Other member function definitions)ostream& operator<<(ostream& out, const clockType& right) {    int h, m, s;    right.getTime(h, m, s);    if (h < 10) {        out << "0";    }    out << h << ":";    if (m < 10) {        out << "0";    }    out << m << ":";    if (s < 10) {        out << "0";    }    out << s;    return out; // Return the ostream object for concatenation}

Overloading the Input Operator (>>)

Similarly, we can overload the input operator to allow users to enter the time directly.

// clockTypeB.h// ... (Previous code)istream& operator>>(istream&, clockType&); // Non-member function declaration
// clockTypeB.cpp// ... (Previous code)istream& operator>>(istream& in, clockType& right) {    int h, m, s;    cout << "Enter the hours: ";    in >> h;    cout << "Enter the minutes: ";    in >> m;    cout << "Enter the seconds: ";    in >> s;    right.setTime(h, m, s);    return in; // Return the istream object for concatenation}

Correct Return Type for Input/Output Operators

To enable concatenation of input and output operators (like cout << "The time is " << myClock << endl;), we need to return the corresponding stream object (ostream& for output and istream& for input) from the overloaded operator functions. This allows the stream object to be used in subsequent operations.

Overloading the Equality Operator (==)

We can replace the equalTime member function with an overloaded operator== to compare two clockType objects for equality.

// clockTypeD.h// ... (Previous code)bool operator==(const clockType&, const clockType&); // Non-member function declaration
// clockTypeD.cpp// ... (Previous code)bool operator==(const clockType& left, const clockType& right) {    int lh, lm, ls, rh, rm, rs;    left.getTime(lh, lm, ls);    right.getTime(rh, rm, rs);    return (lh == rh && lm == rm && ls == rs);}

Overloading the Increment Operator (++)

The increment operator (++) is a unary operator, meaning it operates on a single operand. We’ll overload it as a member function to increment the clockType object by one second. To handle both pre-increment (++clock1) and post-increment (clock1++), we’ll define two versions of the operator++ function. The post-increment version takes an integer parameter (typically unused) to differentiate it from the pre-increment version.

// clockTypeD.h// ... (Previous code)clockType operator++(); // Pre-increment declarationclockType operator++(int); // Post-increment declaration
// clockTypeD.cpp// ... (Previous code)clockType clockType::operator++() {    incrementSeconds();    return *this;}clockType clockType::operator++(int x) {    clockType temp = *this;    incrementSeconds();    return temp;}

In the post-increment version, we create a temporary copy of the object before incrementing it. This ensures that the original value is returned, as expected in post-increment operations.

By overloading these operators, we can make our clockType class more user-friendly and integrate seamlessly with standard C++ syntax.