C++ Operator Overloading
Lesson outline
What Operator Overloading Is
An operator is a function whose name is punctuation. When you write a + b on two ints the compiler already knows what to do; on a type you wrote yourself it does not, and you have to say. Operator overloading is how you say it. The point is not cleverness - it is that code which reads like the problem is easier to get right than code which reads like the machine.
rent.plus(food) is not wrong, it is just noisy - and it gets worse the moment you need three of them. rent + food + travel is the expression a reader already understands.
Overloading as a Member Function
Written as a member, the left operand is the object the function is called on, so a + b becomes a.operator+(b). Only the right operand is a parameter. Mark it const: adding two amounts must not change either of them.
Compound Assignment, and the Pair That Cannot Disagree
+= is different from +: it modifies the object rather than producing a new one, so it returns a REFERENCE to *this, which is what lets you chain a += b += c. The convention worth learning is to write += first and then define + in terms of it. Written that way the two operators cannot drift apart, because there is only one piece of arithmetic.
Comparison Operators
== answers whether two values are the same. < answers which comes first, and it is the one the library asks for: std::sort and std::map order your type by calling <, so a type without it cannot be sorted or used as a map key. Both should be const, and both take their operand by const& so no copy is made just to compare.
Stream Insertion Must Be a Free Function
This is the rule that catches everyone. std::cout << m means operator<<(std::cout, m) - the LEFT operand is the stream, not your object. A member function's left operand is always the object it belongs to, so << cannot be a member of Money. It has to be a free function taking the stream first. And because it usually needs the private data it is printing, this is the classic reason to declare it a friend.
The Call Operator: Objects That Behave Like Functions
Overloading operator() lets you call an object as though it were a function. Such an object is called a FUNCTION OBJECT, or functor, and it is how the standard library takes a rule as an argument: a comparator handed to std::sort or to a priority queue is exactly this. Unlike a plain function, a function object can carry state - here, which way to sort.
Member or Free Function?
The question answers itself once you ask what the left operand is. If it must be your object, the operator can be a member. If it is something else - a stream, or a built-in type - it has to be a free function.
| Operator | Write it as | Why |
|---|---|---|
+, -, *, / | member or free | either works; a free function also allows 2 * money |
+=, -=, = | member | it modifies the object, so it belongs to it |
==, <, > | member or free | symmetric by nature; free keeps both sides equal |
<<, >> | free, often friend | the left operand is the stream, not your type |
(), [], -> | member, required | the language does not allow these as free functions |
What Not to Overload
An overloaded operator is a promise about meaning. Break the promise and you have written code that looks obvious and behaves otherwise, which is worse than a clumsy method name.
&&and||- overloading them silently loses short-circuit evaluation, so both sides get evaluated every time.,- the comma operator loses its guaranteed left-to-right ordering.&- almost nothing expects taking an address to run your code.- Anything whose meaning would surprise a reader:
+should combine, not print.