TL

14-Operator Overloading

Operator Overloading

  • Operator overloading allows operators like + and << to have additional meanings beyond their standard numerical operations, such as concatenating or printing strings.

Operators vs. Functions

  • The expression a * x + b can be written as sum(product(a, x), b) without operators, but function calls are less intuitive.

  • Operator overloading allows you to define how operators work with your classes.

  • It's recommended to maintain the traditional meaning of operators when overloading them (e.g., + for concatenation, which relates to the sum of numbers).

Overloading Example: A Time Class

  • The Time class represents a time with hours and minutes.

  • The header file time.h declares the Time class with private members hours and minutes, and public methods like Time(), Time(int h, int m), get_hours(), and get_minutes().

Time Class Definition (time.h)

#ifndef TIME_H
#define TIME_H

class Time {
public:
    Time();
    Time(int h, int m);
    int get_hours() const;
    int get_minutes() const;

private:
    int hours;
    int minutes;
};

#endif

Time Class Implementation (time.cpp)

#include "time.h"

Time::Time() {
    hours = 0;
    minutes = 0;
}

Time::Time(int h, int m) {
    hours = h;
    minutes = m;
}

int Time::get_hours() const {
    return hours;
}

int Time::get_minutes() const {
    return minutes;
}

Time Class: Operator-

  • It's possible to compute the difference between two Time objects if you define the operator-.

  • Example:
    cpp Time morning(9, 30); Time lunch(12, 0); int minutes_to_lunch = lunch - morning;

Defining the operator-

int operator-(Time a, Time b) {
    return a.get_hours() * 60 + a.get_minutes() - b.get_hours() * 60 - b.get_minutes();
}
  • You can't create new operators in C++ or change operator precedence.

Syntax: Overloaded Operator Definition

  • The general syntax for defining an overloaded operator is:
    return_type operatoroperator_symbol(parameters) { statements }

  • Example:
    cpp int operator-(Time a, Time b) { return a.get_hours() * 60 + a.get_minutes() - b.get_hours() * 60 - b.get_minutes(); }

  • operator_symbol can be any of the following: +, -, *, /, %, ^, <, >, <<, >>, |, ||, &, &&, ==, !=, <=, >=, !, --, ++, ~, and a few more.

Time Class: Operator- (2)

  • The operator- function can be a non-member function or an accessor member function.

  • Non-member function:
    cpp int operator-(Time a, Time b) { return a.get_hours() * 60 + a.get_minutes() - b.get_hours() * 60 - b.get_minutes(); }

  • Member function:
    cpp int Time::operator-(Time b) const { return (hours - b.get_hours()) * 60 + minutes - b.get_minutes(); }

An Operator Function that Returns a Time

  • It might not make sense to add two Time objects because they represent clock times, not durations.

  • However, it makes sense to add minutes to a Time object.

Example: Adding Minutes to a Time Object

Time operator+(Time a, int minutes) {
    int result_minutes = a.get_hours() * 60 + a.get_minutes() + minutes;
    return Time((result_minutes / 60) % 24, result_minutes % 60);
}

Time too_early(6, 0);
Time wake_up = too_early + 45; // Using the operator

Overloading Comparison Operators

  • To compare two Time objects, overload the == operator.

Example: Overloading ==, !=, and < Operators

bool operator==(Time a, Time b) {
    return a - b == 0;
}

bool operator!=(Time a, Time b) {
    return a - b != 0;
}

bool operator<(Time a, Time b) {
    return a - b < 0;
}
  • If you want to sort an array of Time objects, you need to define the < operator.

Overloading << and >> for Input and Output (1)

  • Overload the << output operator for the Time class:
    cpp ostream& operator<<(ostream& out, Time a) { out << a.get_hours() << ":" << std::setw(2) << std::setfill('0') << a.get_minutes(); return out; }

  • The << operator returns the output stream to enable chaining.

Overloading << and >> for Input and Output (2)

  • Chaining example:
    cpp cout << noon << "\n"; // Equivalent to (cout << noon) << "\n";

  • operator<<(cout, noon) prints the time and returns cout, which then prints a newline.

Overloading << and >> for Input and Output (3)

  • Define the operator>> to read a Time object from an input stream:
    cpp istream& operator>>(istream& in, Time& a) { int hours; char separator; int minutes; in >> hours; in.get(separator); // Read ':' character in >> minutes; a = Time(hours, minutes); return in; }

  • The >> operator also returns the input stream reference istream& to allow chaining.

  • The >> operator must have a parameter of type Time& because it modifies the object.

Main() Program: Test the Time Class & Operator Overloads

  • Example main() function:
    cpp int main() { Time too_early(6, 0), noon(12, 0); Time wake_up = too_early + 45; std::cout << "Wake up at: " << wake_up << std::endl; std::cout << "Current time: "; Time now; std::cin >> now; if (now < wake_up) { std::cout << "Too early!" << std::endl; } if (now == noon) { std::cout << "Time for lunch!" << std::endl; } std::cout << "Minutes to lunch: " << noon - now << std::endl; return 0; }

  • Example Program Run:

Wake up at: 6:45
Current time: 7:25
Minutes to lunch: 275

Operator Members

  • When defining an operator function whose first argument is a class, you can make it a member function to access private data directly.

  • Example:

    class Time {
    public:
        ...
        Time operator+(int min) const;
    };
    
    Time Time::operator+(int min) const {
        int result_minutes = hours * 60 + minutes + min;
        return Time((result_minutes / 60) % 24, result_minutes % 60);
    }
    

Practice It: Operator Overloads

  • Complete the code for a - operator that is a member function of the Time class to return the total minutes between two Time objects:
    cpp int Time::operator-(const Time& other) const { return (hours - other.hours) * 60 + (minutes - other.minutes); }

Practice It: Operator Overload for the string class

  • Implement an operator* function to repeat strings a given number of times:
    cpp std::string operator*(std::string s, int n) { std::string result = ""; for (int i = 0; i < n; ++i) { result += s; } return result; }

  • Example:
    cpp std::string greeting = "hi"; std::string result = greeting * 3; // result is "hihihi"

Special Topic 1: Overloading ++ and -- Operators (1)

  • Overloading ++ and -- operators has some challenges due to prefix (++x) and postfix (x++) forms.

  • Both forms increment x, but they return different values.

  • ++x returns the value of x after incrementing.

  • x++ returns the value of x before incrementing.

Special Topic 1: Overloading ++ and -- Operators (2)

  • The difference between prefix and postfix matters when combining the increment with another expression.

  • Example:
    cpp int x = 4; std::cout << x++ << std::endl; // Prints 4, then x becomes 5 std::cout << x << std::endl; // Prints 5 std::cout << ++x << std::endl; // Increments x to 6, then prints 6

  • Avoid using the return value of increment operators in complex expressions.

  • Use ++ only to increment a variable and ignore the return value.

Special Topic 1: Overloading ++ and –- Operators (3)

  • To overload the prefix and postfix forms, the compiler needs to distinguish between them.

  • Prefix form:
    cpp void operator++(Time& a); // Prefix

  • Postfix form:
    cpp void operator++(Time& a, int dummy); // Postfix

  • The int dummy parameter is not used; it only serves to differentiate the two operator++ functions.

Special Topic 2: Implicit Type Conversions

  • C++ converts operand types to match before performing arithmetic.

  • Example: 6 * 3.1415926 (integer times a floating-point number). The 6 will be converted to a floating point number for the calculation.

Implicit Type Conversion Example

  • You can define implicit type conversions for your own classes.

  • In Worked Example 13.1:
    cpp class Fraction { public: Fraction(int n, int d); // The fraction n/d Fraction(int n); // The fraction n/1 Fraction operator+(Fraction other) const; ... };

  • Two categories of type conversions:

    • From a standard type to the new class.

    • From the class to another type.

Conversion To a Custom Class

  • With the Fraction class example given earlier, conversions to the class are done by constructors.

    Fraction a(3, 4);
    a = a + 2; // Translated to a = a.operator+(Fraction(2))
    
  • Sometimes, constructors shouldn't be type converters. The vector class's constructor with an integer parameter (for initial size) is declared as explicit. This prevents implicit conversions.

Conversion From a Custom Class

  • To enable conversion from a custom class to another type, use a conversion operator.

Fraction::operator double() const {
// Convert numerator to double, then do division
return numerator * 1.0 / denominator;
}
  • Conversion operator syntax: a type as the operator name, no arguments, and no result type (the type is implicit in the name).

  • Usage:

Fraction a(3, 4);
double root = sqrt(a); // Translates as sqrt(a.operator double())

Conversion From a Custom Class: istream to bool

  • The istream class has an operator bool for use in conditional statements.

  • Example:
    cpp while (cin >> x) { ... }

  • The >> operator returns type istream, which isn't a legal condition type, but istream defines operator bool to return true if input is successful and false at the end of input or on failure.

WORKED EXAMPLE 1: A Fraction Class

  • Represent a ratio of two integers, with three constructor formats:

Fraction a(3, 4);   // Represents 3/4
Fraction b(7);         // Represents 7/1
Fraction c;            // Represents 0/1
  • Fraction objects should behave like numbers:

if(a < b)
c = b - a;
else
c = a - b;
cout << "Value is " << c << endl;
  • Should be able to mix with other number types such as in this example:

c = a + 3;   // Should read as the addition of a + 3/1

Designing the Fraction Class: Greatest Common Divisor (1)

  • Reduce arithmetic results to lowest common terms.

  • Example: 1/2 + 3/2 = (1\cdot2 + 2\cdot3)/(2\cdot2) = 8/4 = 2/1

  • Reduction occurs in the constructor:

Fraction::Fraction(int n, int d) {
int g = gcd(n, d); // The greatest common divisor
if (g == 0) {
numerator = 0;
denominator = 1;
} else if (d > 0) {
numerator = n / g;
denominator = d / g;
} else {
numerator = -n / g;
denominator = -d / g;
}
}

Designing the Fraction Class: Greatest Common Divisor (2)

  • The greatest common divisor is computed using Euclid’s algorithm:

int gcd(int a, int b) {
int m = abs(a);
int n = abs(b);
while (n != 0) {
int r = m % n;
m = n;
n = r;
}
return m;
}

Designing the Fraction Class: Subtraction

  • Two subtraction operators exist.

    • Unary: -f

    • Binary: f - g

  • Unary version:

Fraction operator-(Fraction f) {
return f * -1;
}
  • Binary version:

Fraction operator-(Fraction f, Fraction g) {
return f + g * -1;
}
  • Express new operators in terms of existing ones for simplicity.

Designing the Fraction Class: Division

  • Division using the same strategy as subtraction is ideal, but because there is no existing multiplicative inverse, it simplifies the code to implement the member function shown:

Fraction Fraction::inverse() const {
return Fraction(denominator, numerator);
}

Fraction operator/(Fraction f, Fraction g) {
return f * g.inverse();
}

Fraction Class Code: fraction.h (part 1)

#ifndef FRACTION_H
#define FRACTION_H

#include <iostream>

using namespace std;

class Fraction {
public:
Fraction(int n, int d);
Fraction(int n);
Fraction();

/**
* Maes the inverse of this fraction.
*
* @return the inverse (denominator / numerator)
*/
Fraction inverse() const;

Fraction Class Code: fraction.h (part 2)

/**
* Prints this fraction to a stream.
*
* @param out the output stream
*/
void print(ostream& out) const;

/**
* Reads a fraction from a stream and stores the input in this fraction
*
* @param in the input stream
*/
void read(istream& in);

Fraction operator+(Fraction other) const;
Fraction operator*(Fraction other) const;
bool operator<(Fraction other) const;

private:
int numerator;
int denominator;
};

Fraction Class Code: fraction.h (part 3)

Fraction operator-(Fraction f);

Fraction operator-(Fraction f, Fraction g);

Fraction operator*(int n, Fraction f);

Fraction operator/(Fraction f, Fraction g);

bool operator>(Fraction f, Fraction g);

bool operator<=(Fraction f, Fraction g);

bool operator>=(Fraction f, Fraction g);

bool operator==(Fraction f, Fraction g);

bool operator!=(Fraction f, Fraction g);

ostream& operator<<(ostream& out, Fraction f);

istream& operator>>(istream& in, Fraction& a);

#endif

Fraction Class Code: fraction.cpp & fractiondemo.cpp

  • See workedexample1/fraction.cpp for function implementations

  • Here is fractiondemo.cpp:

#include <iostream>

#include "fraction.h"

using namespace std;

int main() {
Fraction f(1, 2);
Fraction g(1, 3);
cout << "f + g: " << f + g << endl;
cout << "f * g: " << f * g << endl;
cout << "f / g: " << f / g << endl;

Fraction Class Code: fractiondemo.cpp (Part 2)

cout << "Enter a fraction: ";
Fraction h;
cin >> h;

if (f == h) {
cout << "Your input equals 1/2" << endl;
} else {
cout << "f - h: " << f - h << endl;
}

return 0;
}