Operator Overloading
By the end of this lesson you'll be able to make your own classes behave like built-in types — adding them with + , comparing them with == and < , indexing them with [] , and printing them with cout << obj — while getting return types and const -correctness right.
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
Part of the free C++ course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Think of an operator like + as a universal plug socket . The built-in types ( int , double ) already fit it. Operator overloading is wiring an adapter so your own type fits the same socket — once Money + Money is wired up, the rest of the language (printing, sorting, totalling) "just works" with your type the way it does with numbers. You're not inventing new syntax; you're teaching a familiar symbol what it means for your class . The skill is wiring the adapter correctly: returning the right thing, and not changing what shouldn't change.
The hardest part isn't the syntax — it's the return type . Operators that build a new value return by value ; operators that change the object return *this by reference . Keep that distinction and most bugs disappear.
1. A Fully-Wired Class: Money
Here's a complete, correct example that overloads every operator this lesson covers on a Money type (stored as whole pence so there's no rounding drift). Read every comment, run it, and check the output. Notice the four shapes you'll reuse forever: arithmetic returns a new object by value , compound assignment returns *this by reference , comparison returns bool and is const , and operator<< is a non-member friend that returns the stream .
2. Member vs Non-Member — How to Choose
When you write an operator as a member , the left operand is always the object itself ( *this ), so it takes one fewer parameter. That's perfect for operators that belong to the object: = , [] , () , -> , and the compound assignments like += . A non-member function is symmetric: both operands are ordinary parameters, so it works even when the left side isn't your class. That's why operator<< must be a non-member — its left operand is cout , not your type — and why + and == are usually non-members too, so 2.0 * vec works as well as vec * 2.0 .
A member operator's left operand is fixed as your object. But cout << m has cout on the left, so operator<< can't be a member — it's a free function taking (ostream&, const Money&) . You mark it friend inside the class only so it can read private fields; it returns ostream& so chained << works.
3. Your Turn: Arithmetic & <<
Time to wire up your own type. The Vector2 below is almost finished — fill in the blanks marked ___ using the hints, then run it. Remember: operator+ and operator* build a new vector, so they return one by value .
4. Subscript [] , Compound += , and ==
Three more shapes to lock in. operator[] returns the element by reference so callers can assign into it ( s[2] = 30 ). operator+= changes the object, so it returns *this by reference — that's what lets you chain (s += 10) += 20 . And operator== compares field-by-field and returns bool . Fill in the blanks below.
operator= (copy assignment) is the one operator with serious hidden danger. If your class manages a raw resource — heap memory, a file handle — and you write a custom destructor, you almost certainly also need a custom copy constructor and copy assignment operator . That's the Rule of Three : those three go together. The compiler's default versions copy pointers shallowly, so two objects end up owning the same memory and both try to free it — a double-free crash.
C++11 adds the move constructor and move assignment , extending it to the Rule of Five . The best advice for beginners is the Rule of Zero : store your data in types that already manage themselves — std::string , std::vector , std::unique_ptr — and the compiler-generated operator= is automatically correct, so you write none of the five.
No blanks this time — just a brief and an outline. Build a Temp class that supports + , < , and printing with << . Run it and check your output against the example in the comments. This is exactly the shape of a real value type.
Practice quiz
How should operator+ return its result?
- By reference to *this
- By value — it builds a brand-new object
- By pointer
- By const reference to a local
Answer: By value — it builds a brand-new object. Arithmetic operators like + create a new value (a fresh object), so they return by value.
What should operator+= return, and how?
- A new object by value
- void
- *this by reference, so calls can chain
- A copy of the right-hand side
Answer: *this by reference, so calls can chain. Compound assignment modifies the existing object and returns *this by reference, enabling chains like (a += b) += c.
Why must operator<< be a non-member function?
- Members can't return references
- Its left operand is the stream (cout), not your class
- It is too large for a class
- Streams forbid member operators
Answer: Its left operand is the stream (cout), not your class. cout << m has cout on the left, so operator<< can't be a member (a member's left operand is always *this); it's a free function.
Why is operator<< often marked friend inside the class?
- To make it run faster
- So it can read the class's private members
- Because friends are required for all operators
- To allow it to modify the stream
Answer: So it can read the class's private members. friend grants the free operator<< access to private fields; if it only prints public data it doesn't even need to be a friend.
What must operator<< return so that cout << a << b chains?
- void
- bool
- ostream& (the stream by reference)
- a copy of the object
Answer: ostream& (the stream by reference). Returning ostream& lets the next << operate on the same stream, so chaining works.
Why does operator[] return the element by reference (E&)?
- To avoid copying the whole container
Returning a reference lets s[2] = 30 write into the element; returning by value would only give a temporary copy.
For a read-only comparison like operator==, what qualifier should it carry?
- static
- const
- virtual
- friend
Answer: const. Comparison operators don't modify the object, so mark them const so they work on const objects and const& parameters.
Given a Money type storing whole pence, what does Money(350) + Money(425) represent?
- £3.50
- £4.25
- £7.75
- £8.25
Answer: £7.75. 350p + 425p = 775p = £7.75; operator+ adds the pence and returns a new Money.
What is the Rule of Zero?
- Never write any operators
- Store data in self-managing types (string, vector, smart pointers) so the compiler-generated special members are correct
- Always write all five special members by hand
- Use only static member functions
Answer: Store data in self-managing types (string, vector, smart pointers) so the compiler-generated special members are correct. The Rule of Zero: rely on members that manage themselves so you write none of the five special members and operator= is automatically correct.
In C++20, what single declaration can generate the comparison operators automatically?
- auto operator<=>(const T&) const = default;
- bool operator==(const T&) = 0;
- operator compare() default;
- using std::compare;
Answer: auto operator<=>(const T&) const = default;. The C++20 spaceship operator <=> defaulted generates the comparisons consistently from one line.