C++
Operator Overloading & References
Two smaller but genuinely useful additions: operator overloading lets a custom class
define what +, ==, or << mean for its own type, so objects
can be used with the same natural syntax as an int. References are a
second way to refer to an existing variable — related to pointers, but with real, useful restrictions
that make a lot of code both safer and easier to read.
- What it is
- Operator overloading: defining what a built-in operator does for a custom type. A reference: an alias for an existing variable, declared with
&after the type. - Mental model
- Operator overloading is teaching
+a new trick for your specific type. A reference is a second name taped onto the same box a variable already lives in — not a new box, and it can never be re-taped onto a different one. - Key numbers
- A reference must be initialized at declaration and can never be reassigned to refer to something else — unlike a pointer, which can be reassigned or left null.
- Reach for it when
- Operator overloading: a type genuinely has a natural mathematical or comparison meaning (a vector, a complex number, a duration). References: passing a large object to a function without copying it, or without ever needing to check for null.
Operator overloading, in a worked example
class Vector2D {
public:
Vector2D(float x, float y) : x(x), y(y) {}
Vector2D operator+(const Vector2D &other) { // defines what + means for two Vector2D objects
return Vector2D(x + other.x, y + other.y);
}
float x, y;
};
Vector2D a(1, 2);
Vector2D b(3, 4);
Vector2D c = a + b; // calls operator+ — c.x is 4, c.y is 6
Without overloading, adding two vectors would need an explicit function call like
add(a, b) — overloading lets the natural + syntax work directly, which reads
closer to the actual math it represents.
Overloading << for printing
The most common operator to overload is <<, letting std::cout print a
custom type directly:
std::ostream& operator<<(std::ostream &out, const Vector2D &v) {
out << "(" << v.x << ", " << v.y << ")";
return out;
}
std::cout << c << std::endl; // (4, 6) — printed directly, no manual field access
References
int x = 5;
int &ref = x; // ref is a REFERENCE to x — another name for the exact same variable
ref = 10; // this actually changes x
std::cout << x << std::endl; // 10
ref isn't a copy of x and isn't a separate variable holding x's
address — it is x, under a second name. This is exactly why passing a reference to
a function achieves the same "modify the caller's variable" effect as passing a pointer in
C's Pointers, without needing & at the call site
or * to access the value:
void increment(int &x) { // reference parameter
x = x + 1; // no * needed — x IS the caller's variable
}
int n = 5;
increment(n); // no & needed at the call site either
std::cout << n << std::endl; // 6
References vs. pointers
| Pointer | Reference | |
|---|---|---|
| Can be null | Yes | No — always refers to something real |
| Can be reassigned | Yes, to point elsewhere | No — bound permanently at declaration |
| Needs explicit syntax to use | Yes — *p to dereference | No — used exactly like the original variable |
| Must be initialized immediately | No | Yes — a reference cannot be declared and assigned later |
In practice: reach for a reference whenever you know the thing being referred to genuinely exists and will never need to change to something else — a function parameter is the classic case. Reach for a pointer specifically when you need the flexibility a reference deliberately gives up: the ability to be null, or to be reassigned.
Real-world applications
- Math/physics types — vectors, matrices, and complex numbers are the textbook case for operator overloading —
+,-, and*already have an obvious, natural meaning for them. - Debug printing — overloading
<<for every custom type in a codebase is standard practice, makingstd::cout << myObjectwork everywhere instead of writing a separate print function per type. - Function parameters — passing a large object (a big struct, a container) by reference instead of by value avoids an expensive copy on every call — the default, idiomatic choice in real C++ code whenever the function doesn't need its own separate copy.
Gotchas
- Overloading an operator to do something that surprises a reader — making
+perform subtraction, for instance — is a real, well-known anti-pattern. Only overload an operator when its built-in meaning is a genuinely natural fit. - A reference must be initialized the moment it's declared —
int &ref;alone is a compile error, unlike a pointer, which can legally be declared now and assigned later. - Returning a reference to a local variable is exactly as dangerous as returning a pointer to one (see C's Functions) — the referenced stack memory is gone the instant the function returns, leaving a reference to nothing valid.
- Pass large objects by
constreference (const Vector2D &v) when a function only needs to read them — this avoids the copy of pass-by-value while still preventing the function from modifying the caller's object, the best of both.