Chapter 23: Operator Overloading, Overloaded Typecasts, and Copy Constructor

Introduction

In C++, operators are implemented as functions. This chapter covers how to overload operators, implement overloaded typecasts, and define copy constructors.

Rules for Operator Overloading

Overloading Operators Using Friend Functions

Basic Example

class Cents {
private:
    int m_cents {};
public:
    Cents(int cents) : m_cents{ cents } { }
    int getCents() const { return m_cents; }

    friend Cents operator+(const Cents& c1, const Cents& c2); // add Cents + Cents using a friend function

    friend Cents operator+(const Cents& c1, const Cents& c2) // overloading inside a class
    {
        return Cents { c1.m_cents + c2.m_cents };
    }
};

Cents operator+(const Cents& c1, const Cents& c2) // note: this function is not a member function!
{
    return Cents { c1.m_cents + c2.m_cents }; // access m_cents directly because this is a friend function
}

Overloading Operators of Different Types

Cents operator+(const Cents& c1, int value)
{
    return Cents { c1.m_cents + value };
}

Cents operator+(int value, const Cents& c1)
{
    return Cents { c1.m_cents + value };
}

Calling Overloaded Operators within Overloaded Operators

MinMax operator+(int value, const MinMax& m)
{
    return m + value; // call operator+(MinMax, int)
}

Overloading Operators Using Normal Functions

Prefer overloading operators as normal functions instead of friends if it’s possible to do so without adding additional functions.

Overloading the I/O `<<` and `>>` Operators

Example

class Point {
private:
    double m_x{};
    double m_y{};
    double m_z{};
public:
    Point(double x=0.0, double y=0.0, double z=0.0): m_x{x}, m_y{y}, m_z{z} {}

    friend std::ostream& operator<< (std::ostream& out, const Point& point);
};

std::ostream& operator<< (std::ostream& out, const Point& point)
{
    out << "Point(" << point.m_x << ", " << point.m_y << ", " << point.m_z << ')';
{
    double x{};
    double y{};
    double z{};

    in >> x >> y >> z;

    if (in)
        point = Point{x, y, z}; // overwrite our existing point

    return in;
}

Point point{};
std::cin >> point;
std::cout << "You entered: " << point;

Overloading Operators Using Member Functions

Example

class Point {
private:
    double m_x{};
    double m_y{};
    double m_z{};
public:
    Point(double x=0.0, double y=0.0, double z=0.0): m_x{x}, m_y{y}, m_z{z} {}

    Point operator+ (int value) const
    {
        return Point { m_x + value, m_y, m_z };
    }
};

When to Use Which

Unary Operators `+`, `-`, `!`, `++`, and `[]`

Unary Minus Operator

class Cents {
private:
    int m_cents {};
public:
    Cents(int cents) : m_cents{ cents } { }
    int getCents() const { return m_cents; }

    Cents operator-() const
    {
        return Cents{-m_cents}; // return a new Cents object with the negated value
    }
};

Increment Operators

class Digit {
private:
    int m_digit {};
public:
    Digit(int digit) : m_digit{ digit } { }

    Digit& operator++() // No parameter means this is prefix operator++
    {
        ++m_digit;
        return *this;
    }

    Digit operator++(int) // int parameter means this is postfix operator++
    {
        Digit temp{*this}; // Create a temporary variable with our current digit
        ++(*this); // Use prefix operator to increment this digit
        return temp; // return saved state
    }
};

Subscript Operator `[]`

class IntList {
private:
    int m_list[10]{};
public:
    int& operator[] (int index)
    {
        return m_list[index];
    }

    const int& operator[] (int index) const
    {
        return m_list[index];
    }
};

Parenthesis Operator `()`

Example

class Matrix {
private:
    double m_data[4][4]{};
public:
    double& operator()(int row, int col)
    {
        assert(row >= 0 && row < 4);
        assert(col >= 0 && col < 4);
        return m_data[row][col];
    }
};

Overloaded Typecasts

Example

class Cents {
private:
    int m_cents {};
public:
    Cents(int cents) : m_cents{ cents } { }

    operator int() const { return m_cents; } // overloaded int cast
};

Assignment Operator `=`

Example

class Fraction {
private:
    int m_numerator{};
    int m_denominator{};
public:
    Fraction(int numerator = 0, int denominator = 1)
        : m_numerator{ numerator }, m_denominator{ denominator } { }

    Fraction& operator= (const Fraction& fraction)
    {
        if (this == &fraction)
            return *this;

        m_numerator = fraction.m_numerator;
        m_denominator = fraction.m_denominator;
        return *this;
    }
};

Deleting the Copy Constructor

class Fraction {
public:
    Fraction(const Fraction &copy) = delete;
};

The default copy constructor and default assignment operators do shallow copies, which is fine for classes that contain no dynamically allocated variables. Classes with dynamically allocated variables need to have a copy constructor and assignment operator that do a deep copy.