Operator Overloading

Nearly every operator in C++, whether it’s +, <<, &&, or even new, can be given unique functionality when used with our own classes. In other words, we can “overload” their default behaviors for specific purposes.

The program below contains an Integer class that overloads the +, -, *, and / operators, allowing us to add, subtract, multiply, and divide different Integer objects:

Integer.h:

#ifndef INTEGER_H
#define INTEGER_H

#include <iostream>

class Integer {
    public:
        int value;

        Integer(int value);
        Integer operator+(const Integer &otherInt);
        Integer operator-(const Integer &otherInt);
        Integer operator*(const Integer &otherInt);
        Integer operator/(const Integer &otherInt);
};

#endif

Integer.cpp:

#include "Integer.h"

Integer::Integer(int value) {
    this->value = value;
}

Integer Integer::operator+(const Integer &otherInt) {
    return Integer(value + otherInt.value);
}
Integer Integer::operator-(const Integer &otherInt) {
    return Integer(value - otherInt.value);
}
Integer Integer::operator*(const Integer &otherInt) {
    return Integer(value * otherInt.value);
}
Integer Integer::operator/(const Integer &otherInt) {
    if (otherInt.value == 0) {
        std::cout << "Warning: division by 0.\n";
        return Integer(0);
    }
    return Integer(value / otherInt.value);
}

main.cpp:

#include <iostream>

#include "Integer.h"

int main() {
    Integer num1(5);
    Integer num2(2);
    Integer num3(0);

    Integer sum = num1 + num2;
    Integer difference = num3 - num2;
    Integer product = num1 * num2;
    Integer quotient = num1 / num3;

    std::cout << "Sum: " << sum.value << "\n";
    std::cout << "Difference: " << difference.value << "\n";
    std::cout << "Product: " << product.value << "\n";
    std::cout << "Quotient: " << quotient.value << "\n";

    return 0;
}

Program Output:

Warning: division by 0.
Sum: 7
Difference: -2
Product: 10
Quotient: 0

Overloaded operators basically act as special functions, with the function name being “operator” followed by the operator’s symbol. Each overloaded operator also can have its own return type and parameter list, just like a normal function.

  • The return type of each overloaded operator above is Integer because each operation, when performed, results in an Integer.
  • The parameter for each overloaded operator above is “const Integer &otherInt” (a constant reference to an Integer). The parameters are const because they should not be modified by the overloaded operators, and they are references to avoid copying Integer objects.
    • For the +, -, *, and / operators, the parameter refers to the second value used in each operation. For example, in “num1 + num2” (line 10 of main.cpp), num1‘s operator+ is used, and the operator’s otherInt parameter becomes a const reference to num2. The operator overload returns a new Integer object whose value is equal to the sum of num1 (this)’s value and num2 (otherInt)’s value.
    • Overloads for the +, -, *, and / operators must only have one parameter since only one value is used after those operators. Similarly, overloads for “unary” operators such as ++ and -- must not have any parameters, since those operators only act on a single value.
    • Using references is not totally necessary here since the Integer class only contains one int (and some functions, but those don’t contribute to class size). Therefore, because Integer objects don’t take up a large amount of memory, copying them is not a difficult process. Most of the time, however, using references will be the best choice here.

Note: The above program uses the = operator multiple times (e.g. Integer sum = num1 + num2;). Unlike most other operators, this operator has default behavior (with some exceptions – see “Deleted implicitly-declared copy assignment operator”). Specifically, it takes each variable from the object to the right of the equals sign, and it copies each of those values into the object on the left hand side. (In other words, it behaves as expected).

Here is a list of all operators that support overloading:

+-*/^&
|~!%=<
><=>=++--<<
>>==!=&&||+=
-=*=/=%=^=&=
|=<<=>>=[]()->
->*,newnew[]deletedelete[]
(There’s also an operator”” that I’ll go over on the next page.)

For a complete reference on how to overload each of these operators, take a look here. Remember that each operator overload only supports a specific number of parameters.

Below, I’ll walk through some of the more important operators not used in the Integer class.

+=, -=, *=, etc.

These operators are all expected to return a value, even though it’s not often used. Specifically, they should return the left-hand value after the operation takes place. For example:

  int x = 5;
  int y = (x += 2); //Now x and y are both equal to 7

Here’s what some of these operators would look like in the Integer class:

Integer.h:

#ifndef INTEGER_H
#define INTEGER_H

#include <iostream>

class Integer {
    public:
        int value;

        Integer(int value);

        Integer &operator+=(const Integer &otherInt);
        Integer &operator-=(const Integer &otherInt);
        Integer &operator*=(const Integer &otherInt);
        Integer &operator/=(const Integer &otherInt);
};

#endif

Integer.cpp:

#include "Integer.h"

Integer::Integer(int value) {
    this->value = value;
}

Integer &Integer::operator+=(const Integer &otherInt) {
    value += otherInt.value;
    return *this;
}
Integer &Integer::operator-=(const Integer &otherInt) {
    value -= otherInt.value;
    return *this;
}
Integer &Integer::operator*=(const Integer &otherInt) {
    value *= otherInt.value;
    return *this;
}
Integer &Integer::operator/=(const Integer &otherInt) {
    value /= otherInt.value;
    return *this;
}

main.cpp:

#include <iostream>

#include "Integer.h"

int main() {
    Integer num1(25);
    Integer num2(3);
    Integer num3 = (num1 *= num2);

    std::cout << num1.value << "\n";
    std::cout << num2.value << "\n";
    std::cout << num3.value << "\n";

    return 0;
}

Program Output:

75
3
75

Each operator returns an Integer reference (to avoid copying) with a value of “*this“, which is the dereferenced “this” pointer (pointer to the current object). Because the current object, or this, represents the left-hand side of the operator and the parameter the right-hand side, returning the current object will provide the expected behavior.

++ and – –

Remember from Using Variables in Calculations how ++ and -- before a variable (prefix operations) work differently from ++ and -- after a variable (postfix operations)? The difference lies in the return value.

The prefix operations increment/decrement a variable and return that incremented/decremented value, whereas the postfix operations return the original value of that variable. Our overloaded ++ and -- operators should behave this way too.

To distinguish between the prefix and postfix operation overloads in code, the prefix operation has no parameters, whereas the postfix operation has an int parameter that can be unused (meaning that no parameter name needs to be given).

Here’s what this all looks like:

Integer.h:

#ifndef INTEGER_H
#define INTEGER_H

#include <iostream>

class Integer {
    public:
        int value;

        Integer(int value);

        Integer &operator++(); //Prefix increment
        Integer operator++(int); //Postfix increment

        Integer &operator--(); //Prefix decrement
        Integer operator--(int); //Postfix decrement
};

#endif

Integer.cpp:

#include "Integer.h"

Integer::Integer(int value) {
    this->value = value;
}

//Prefix increment
Integer &Integer::operator++() {
    ++value;
    return *this;
}
//Postfix increment
Integer Integer::operator++(int) {
    Integer oldInt = *this;
    ++value;
    return oldInt;
}

//Prefix decrement
Integer &Integer::operator--() {
    --value;
    return *this;
}
//Postfix decrement
Integer Integer::operator--(int) {
    Integer oldInt = *this;
    --value;
    return oldInt;
}

main.cpp:

#include <iostream>

#include "Integer.h"

int main() {
    Integer num1(8);
    Integer num2 = ++num1;
    std::cout << num1.value << " " << num2.value << "\n";

    Integer num3(8);
    Integer num4 = num3--;
    std::cout << num3.value << " " << num4.value << "\n";

    return 0;
}

Program Output:

9 9
7 8

Like before, we return a reference to “*this” for the prefix operators. However, for the postfix operators, we first store a copy of the original Integer object and then return that copy. We can’t return a reference to oldInt because that variable goes out of scope at the end of the function, and, when a variable goes out of scope, all references to that variable break.

<<

By overloading the << operator, we can easily use our classes with std::cout. However, because of how the operator works (with std::cout on the left-hand side), our operator overload needs to be “global” (not inside of a class).

Below is a program overloading << for the Integer class. Note that std::cout is an instance of the std::ostream class.

Integer.h:

#ifndef INTEGER_H
#define INTEGER_H

#include <iostream>

class Integer {
    public:
        int value;

        Integer(int value);
};

#endif

Integer.cpp:

#include "Integer.h"

Integer::Integer(int value) {
    this->value = value;
}

main.cpp:

#include <iostream>

#include "Integer.h"

std::ostream &operator<<(std::ostream &os, const Integer &integer) {
    os << "Integer: " << integer.value;
    return os;
}

int main() {
    Integer num(8);
    std::cout << num << "\n";
    return 0;
}

Program Output:

Integer: 8

When using the << operator, the first parameter to the overload is a reference to the left-hand side of the operator (usually the std::cout object), and the second parameter is a reference to the right-hand side.

The first parameter is not const, but is a reference, allowing us to directly modify the first variable passed to the overload (usually std::cout). A good way of thinking about this is by replacing the os parameter with std::cout in your head.

At the end of the overload, we return the modified os object. This allows us to chain together uses of the << operator like so:

Integer num1(8);
Integer num2(4);
std::cout << num1 << ", " << num2 << "\n";
  • std::cout << num1 returns the modified std::cout.
  • Therefore, chaining << ", " is equivalent to calling:
std::cout << num1;
std::cout << ", ";
  • An operator overload already exists for using strings with std::ostream objects, which also returns a modified std::ostream object. Therefore, << ", " returns another modified std::cout.
  • And this works the same for num2 and "\n".

In the end, we get one std::cout object with all that text added to it, which is then displayed on the screen.

Friend Functions

If we want the << operator to be able to access private variables in the Integer class, we need to make that operator overload be a “friend” of the class. Usually friend function implementations are placed in the friend class’s “.cpp” file (rather than main.cpp).

Here’s what this looks like when making the value variable of the Integer class private:

Integer.h:

#ifndef INTEGER_H
#define INTEGER_H

#include <iostream>

class Integer {
    private:
        int value;

    public:
        Integer(int value);

        friend std::ostream &operator<<(std::ostream &os, const Integer &integer);
};

#endif

Integer.cpp:

#include "Integer.h"

Integer::Integer(int value) {
    this->value = value;
}

std::ostream &operator<<(std::ostream &os, const Integer &integer) {
    os << "Integer: " << integer.value;
    return os;
}

main.cpp:

#include <iostream>

#include "Integer.h"

int main() {
    Integer num(8);
    std::cout << num << "\n";
    return 0;
}

Program Output:

Integer: 8

Different Parameter Types

We can also allow our classes to interact with other data types by changing the parameters of our overloaded operators. For example, this operator overload would allow us to add ints to Integer objects:

Integer Integer::operator+(const int otherInt) {
    return Integer(value + otherInt);
}

Here, we use a const int rather than a const int reference because the performance difference between copying an int and taking a reference is insignificant; ints are only about 4 bytes of data.

By applying this operator overload, the below code would work:

Integer num1(4);
Integer num2 = num1 + 5;

Final Note

We can make operators do anything we want with our classes, but it’s a good idea to always stick to what’s expected of each operator. For example, you probably don’t want to overload the , (comma) operator to perform addition or the ~ operator to perform division. Doing so just makes code harder for other people (and future you) to understand.

Challenge Problem

Create your own Integer class that includes the following operators: <, >, ==, and != . Each of these operators returns a value of type bool and has one parameter representing the value on the right-hand side of the operator.

If you get stuck, check out my solution here.