Operator Overloading & Resource Management

Operator Overloading

Why this matters

1 + 2 and 1.0 + 2.0 both just work because + is built into the language for int and double. Module 4 mentioned, without explaining how, that std::cout << x works through an overloaded << operator – the same mechanism that lets a + b on the worked example’s two Vector2Ds produce a sensible result, instead of a compile error asking what + is supposed to mean for a type you invented. Operator overloading is how you teach your own classes to participate in the same operator syntax everyone already knows.

The general shape: operator followed by the symbol

Vector2D operator+(const Vector2D &other) const {
    return Vector2D(x + other.x, y + other.y);
}

operator+ is just a function with an unusual name – operator followed directly by the symbol you’re defining. Written as a member function like this, it takes one explicit parameter (other, the right-hand side); the left-hand side is implicitly *this, the same as any other member function. a + b is really a.operator+(b) underneath – the + syntax is just a much more readable way to write that call, resolved by the compiler at compile time.

Mark it const, exactly as Module 4 covered: operator+ reads both Vector2Ds but modifies neither, so the promise applies here identically. It returns a new Vector2D by value – a + b shouldn’t change a or b at all, only produce a third value, the same expectation you already have for 1 + 2.

operator==: comparing member-wise

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

Same shape, different return type: bool, true when every member matches. There’s no automatic member-wise comparison the way there’s automatic member-wise copying (next lesson covers that) – you write out exactly what “equal” means for your type, one member at a time.

operator+=: modifying *this, and returning a reference to it

operator+ produces a brand-new object and leaves both original operands untouched. Its compound cousin, operator+=, does the opposite on purpose – it modifies the left-hand object in place:

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

Two differences from operator+ worth noticing together: it’s not const (it modifies the object it’s called on, so it can’t promise otherwise), and it returns Vector2D & – a reference to *this, the same object that was just modified, not a new one. Returning *this by reference is what lets a += b += c; chain the way you’d expect, mirroring how built-in += behaves for int and double. Getting this return type wrong (returning void, or a copy instead of a reference) compiles, but silently breaks chaining and wastes a copy – worth writing it this exact way as a matter of habit.

operator<< has to be a free function – and here’s why

The worked example’s operator<< looks different from the other two: it’s declared outside Vector2D entirely, taking two explicit parameters (std::ostream &out, const Vector2D &v), not one.

This isn’t a style choice – it’s required. std::cout << a means “call operator<< with std::cout as the left-hand side and a as the right.” If operator<< were a member function, it would have to be a member of whatever type is on the left – which is std::ostream, a type you don’t own and can’t add members to. The only way to make << work with std::ostream on the left and your type on the right is a free function taking both operands explicitly. It returns std::ostream & – a reference to the same stream it was given – so that std::cout << a << std::endl; can keep chaining further << calls onto the result, exactly the way chained std::cout calls have worked since Module 4.

The general rule this generalizes to: write an overloaded operator as a member function when your class is naturally the left-hand operand (vector + vector, account.deposit-style operators); write it as a free function when the left-hand operand is something you don’t own, or when you want both sides treated identically.

Overload operators for operations that already have an obvious meaning

One piece of judgment worth stating explicitly: overload an operator only when there’s already an unambiguous, conventional meaning for it on your type – + for vector addition is immediately obvious to any reader; + on a type where it would mean something surprising (concatenation, merging, anything not analogous to arithmetic addition) creates code that looks ordinary but does something a reader wouldn’t guess. When an operation doesn’t have that kind of obvious analogy, an ordinarily-named member function communicates intent far better than stretching an operator to fit.

Try it
Output will appear here.

Exercises