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;
}
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
- Member function:
+=,-=,[],(),++,--, unary- - Free (or friend) function:
<<,>>, binary+-*(for symmetry)
Always define related operators together
If you define ==, also define !=. If you define <, consider defining >, <=, >= too.
Related Articles
- C++ Classes and Objects — understand classes before overloading their operators
- C++ Functions Tutorial — operator overloading is just function overloading with special names
- C++ Pass by Value and Reference — essential for understanding operator parameter types
- OOP in C++ — operator overloading in the broader context of object-oriented design
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.