Skip to content
C++ Better Explained
Go back
C++ Operator Overloading: A Beginner's Guide
Edit page

C++ Operator Overloading: A Beginner’s Guide

In C++, you can write 3 + 4 to add integers and "hello" + " world" to concatenate strings. Both use the + operator, but they do very different things. That’s because the standard library has already defined what + means for those types.

Operator overloading lets you do the same for your own classes. If you write a Vector2D class, you can define + so that v1 + v2 adds the components — rather than writing v1.add(v2). The result is cleaner, more readable code that behaves the way you’d expect.

This tutorial explains how to overload operators in C++, with practical examples.


Why Overload Operators?

Without operator overloading:

Vector2D result = v1.add(v2);
if (v1.equals(v2)) { ... }
cout << v1.toString();

With operator overloading:

Vector2D result = v1 + v2;
if (v1 == v2) { ... }
cout << v1;

The second version is immediately readable. Operators express intent clearly because everyone already knows what +, ==, and << mean.


Your First Overloaded Operator: +

Let’s build a simple Vector2D class and overload +:

#include <iostream>
using namespace std;

class Vector2D {
public:
    double x, y;

    Vector2D(double x, double y) : x(x), y(y) {}

    // Overload + as a member function
    Vector2D operator+(const Vector2D& other) const {
        return Vector2D(x + other.x, y + other.y);
    }
};

int main() {
    Vector2D v1(1.0, 2.0);
    Vector2D v2(3.0, 4.0);
    Vector2D v3 = v1 + v2;  // Calls operator+

    cout << v3.x << ", " << v3.y << endl;  // 4, 6
    return 0;
}

When the compiler sees v1 + v2, it translates it to v1.operator+(v2). The member function receives v1 as this and v2 as other.

The const at the end of the signature means this operator doesn’t modify the object. The parameter is const Vector2D& — a const reference to avoid copying.


Overloading ==

Equality comparison is one of the most useful operators to overload:

bool operator==(const Vector2D& other) const {
    return (x == other.x) && (y == other.y);
}

bool operator!=(const Vector2D& other) const {
    return !(*this == other);  // Reuse == to avoid duplication
}

Notice operator!= is implemented in terms of operator==. This is a good pattern — define one, implement the other using it.

Usage:

Vector2D v1(1.0, 2.0);
Vector2D v2(1.0, 2.0);
Vector2D v3(5.0, 5.0);

cout << (v1 == v2) << endl;  // 1 (true)
cout << (v1 == v3) << endl;  // 0 (false)

Overloading << for Output

The output operator << is one you’ll use constantly for debugging. It’s a free function (or friend function), not a member, because the left-hand side is ostream, not your class:

#include <iostream>
using namespace std;

class Vector2D {
public:
    double x, y;
    Vector2D(double x, double y) : x(x), y(y) {}

    // Declare << as a friend so it can access private members
    friend ostream& operator<<(ostream& os, const Vector2D& v);
};

ostream& operator<<(ostream& os, const Vector2D& v) {
    os << "(" << v.x << ", " << v.y << ")";
    return os;  // Return os to allow chaining
}

int main() {
    Vector2D v(3.0, 4.0);
    cout << v << endl;          // (3, 4)
    cout << "Vector: " << v << " done" << endl;  // Chaining works
    return 0;
}

Returning os (the stream) is important — it’s what allows chaining: cout << a << b << c.


Overloading += and Other Compound Assignments

Compound assignment operators (+=, -=, *=) are typically member functions that modify *this and return a reference:

Vector2D& operator+=(const Vector2D& other) {
    x += other.x;
    y += other.y;
    return *this;  // Return reference to this object
}

Vector2D& operator-=(const Vector2D& other) {
    x -= other.x;
    y -= other.y;
    return *this;
}

Usage:

Vector2D v1(1.0, 2.0);
Vector2D v2(3.0, 4.0);
v1 += v2;
cout << v1 << endl;  // (4, 6)

Once you have +=, you can implement + using it (a clean pattern):

// Free function: creates a copy, modifies it, returns it
Vector2D operator+(Vector2D lhs, const Vector2D& rhs) {
    lhs += rhs;  // Reuse +=
    return lhs;
}
If you're looking to go deeper with C++, the C++ Better Explained Ebook is perfect for you — whether you're a complete beginner or looking to solidify your understanding. Just $19.

Overloading [] (Subscript)

The subscript operator is useful for container-like classes:

class IntArray {
    int data[10];
public:
    // Non-const version: allows modification
    int& operator[](int index) {
        return data[index];
    }

    // Const version: read-only access
    const int& operator[](int index) const {
        return data[index];
    }
};

IntArray arr;
arr[0] = 42;       // Uses non-const version (writes)
cout << arr[0];    // Uses non-const version (reads)

const IntArray& ref = arr;
cout << ref[0];    // Uses const version

Both versions are needed: one for reading from const objects, one for modifying non-const objects.


Overloading ++ (Increment)

Pre-increment (++v) and post-increment (v++) are separate operators with different signatures:

class Counter {
public:
    int value;
    Counter(int v) : value(v) {}

    // Pre-increment: ++c (increment, return new value)
    Counter& operator++() {
        ++value;
        return *this;
    }

    // Post-increment: c++ (return old value, then increment)
    Counter operator++(int) {  // The int parameter is a dummy to distinguish
        Counter old = *this;
        ++value;
        return old;
    }
};

Counter c(0);
cout << (++c).value << endl;  // 1 (incremented before use)
cout << (c++).value << endl;  // 1 (old value returned, then incremented)
cout << c.value << endl;      // 2

Prefer pre-increment when the return value isn’t needed — it avoids the copy that post-increment requires.


A Complete Example: Money Class

Here’s a class that brings several operators together:

#include <iostream>
#include <iomanip>
using namespace std;

class Money {
    int cents;  // Store as integer cents to avoid float rounding

public:
    Money(int dollars, int cents_) : cents(dollars * 100 + cents_) {}
    explicit Money(double amount) : cents(static_cast<int>(amount * 100 + 0.5)) {}

    Money operator+(const Money& other) const {
        Money result(0, 0);
        result.cents = cents + other.cents;
        return result;
    }

    bool operator==(const Money& other) const { return cents == other.cents; }
    bool operator<(const Money& other) const  { return cents < other.cents; }

    friend ostream& operator<<(ostream& os, const Money& m) {
        os << "$" << (m.cents / 100) << "."
           << setw(2) << setfill('0') << (m.cents % 100);
        return os;
    }
};

int main() {
    Money price(9, 99);
    Money tax(0, 75);
    Money total = price + tax;

    cout << price << " + " << tax << " = " << total << endl;
    // $9.99 + $0.75 = $10.74
    return 0;
}

Rules and Guidelines

Which operators can’t be overloaded?
:: (scope), . (member access), .* (member pointer), ?: (ternary), sizeof.

Don’t change semantics
If + on your class doesn’t behave like addition, users will be confused. Operator overloading is for making code more natural, not for being clever.

Member vs free function

Always define related operators together
If you define ==, also define !=. If you define <, consider defining >, <=, >= too.



Take Your C++ Further

If you’re looking to go deeper with C++, the C++ Better Explained Ebook is perfect for you — whether you’re a complete beginner or looking to solidify your understanding. Just $19.

👉 Get the C++ Better Explained Ebook — $19


Edit page
Share this post on:

Previous Post
C++ Lambda Functions Explained: A Beginner's Guide
Next Post
C++ Random Numbers: rand() vs mt19937 (and Which to Use)