Lesson 07 / 14
Operator Overloading
Measures why operator+ returns a new temporary object, and why operator+= instead returns its own object by reference.
Published Updated
In this lesson
- Measures that operator+ returns a new temporary object
- Distinguishes that operator+= modifies its own object and returns it by reference
- Applies limits when deciding which operators are meaningful for a class
Contents
This is the last lesson in this section of K04. The previous lesson measured that a reference works with the caller’s own object without producing any copy. This lesson measures that an operator sometimes does exactly the opposite — it produces a new object. The measurement kind is temporary: the result of the expression a + b is neither a nor b; it is a new Money object, constructed inside the operator’s body, that ceases to exist at the end of the expression.
In C, the only way to add such values was a function with an explicit name: a function like Money add_up(Money a, Money b) was called, and its result was assigned to a separate variable — writing a + b was not possible, because + was defined only for the built-in numeric types. In C++, overloading operator+ opens up the + syntax for a user-defined type like Money too; the caller writes a + b instead of add_up(a, b). This increases the readability of the syntax, but it also hides what the body does (whether it constructs a new object or modifies an existing one) — this is exactly what this lesson’s measurement uncovers: which object is produced behind the operator’s syntax.
What Operator Overloading Is
Operator overloading is defining what a built-in operator such as +, ==, or << means for a user-defined class. When operator+ is written as a member function, the expression a + b is read by the compiler as a.operator+(b) — the syntax stays +, but what gets called is a function the class itself writes. Overloading does not add a new syntax; it gives meaning to the existing operator syntax for user-defined types.
Overloading cannot change how many arguments an operator takes, or its precedence — + always stays binary, and it continues to be evaluated after *. The only thing that can change is what the operator does with its arguments. This limit is deliberate: someone calling operator+ expects the “takes two values and produces a result” behavior for Money, just as for int; overloading cannot make a syntactic change that breaks this expectation, it can only define what the “result” and “modification” part means.
// money.hpp — a class holding an amount in cents, for operator overloading
#ifndef MONEY_HPP
#define MONEY_HPP
#include <cstdio>
class Money {
public:
explicit Money(long cents) : cents_(cents) {}
Money operator+(const Money& other) const {
return Money(cents_ + other.cents_);
}
Money& operator+=(const Money& other) {
cents_ += other.cents_;
return *this;
}
bool operator==(const Money& other) const {
return cents_ == other.cents_;
}
long cents() const { return cents_; }
private:
long cents_;
};
inline void use_money(const Money& m) {
std::printf("cents=%ld\n", m.cents());
}
#endif
operator+: Returns a New Temporary
// plus-measurement.cpp — is the result of a + b a new temporary
#include <cstdio>
#include "money.hpp"
int main() {
Money a(100);
Money b(250);
Money c = a + b;
std::printf("is c the same object as a %s\n", &c == &a ? "yes" : "no");
std::printf("is c the same object as b %s\n", &c == &b ? "yes" : "no");
use_money(a);
use_money(b);
use_money(c);
return 0;
}
is c the same object as a no is c the same object as b no cents=100 cents=250 cents=350
CR15. The expression a + b turns into the call a.operator+(b); the body of operator+, return Money(cents_ + other.cents_);, constructs a new Money and returns it by value — it modifies neither a nor b, and both keep their own cents_ value (100 and 250). The returned object is constructed into c; c’s address is neither the same as a’s nor as b’s. operator+ being marked const (operator+(const Money& other) const) guarantees that neither other nor the object it is called on (this) is modified — by definition, an addition operator must produce only the result, not the operands being added.
operator+=: Modifies Its Own Object and Returns It by Reference
// plus-equals-measurement.cpp — does a += b modify a itself
#include <cstdio>
#include "money.hpp"
int main() {
Money a(100);
Money b(250);
Money& result = (a += b);
std::printf("is result the same object as a %s\n", &result == &a ? "yes" : "no");
use_money(a);
return 0;
}
is result the same object as a yes cents=350
CR16. The body of operator+=, cents_ += other.cents_;, modifies its own cents_ member, then return *this; returns itself by reference — it constructs no new object. When a += b is called, a itself changes (it becomes 350), and the result of (a += b) is again a itself: &result == &a is true. This is the exact opposite of operator+: operator+ produces a new temporary without modifying either operand, while operator+= modifies a single object (the left one) and returns a reference of kind self. The two operators’ return types carry this difference in their signature — operator+ returns by value (Money), operator+= by reference (Money&); reading the signature alone tells in advance which one produces a new object and which one modifies an existing one.
The Limits of Defining a Meaningful Operator
The fact that an operator can be overloaded for a user-defined class does not mean every class must define every operator. A few conventions keep operator definitions consistent:
- Binary arithmetic operators like
+,-,*are usually written as free functions, not members (Money operator+(const Money& a, const Money& b)), because this provides symmetric conversion for expressions whose left side is notMoneyeither (for example, if anintwere to be added to aMoneyin the future).Moneydefinesoperator+as a member for this lesson’s measurement; the free-function form is only mentioned here. +can be written in terms of+=: instead ofcents_ + other.cents_, the body ofoperator+could also be written asMoney result(cents_); result += other; return result;— basing+on+=without repeating+=‘s logic prevents the two operators’ behavior from drifting apart.Moneywrites the two independently to keep this lesson’s measurement simple.==is an equality, not an ordering;Money::operator==compares the two objects’cents_values, and an ordering operator like<is not defined in this lesson.<<(stream output) cannot be a member function — in the expressionstd::cout << m, the left side isstd::ostream, notMoney; that is whyoperator<<is written as a free function, and if it needs to accessMoney’sprivatemember, access is granted with afrienddeclaration (mentioned only by name in lesson 02). This lesson does not defineoperator<<; it does the same job with an ordinary function nameduse_money.
The question that must be asked when overloading an operator is this: does this operator’s built-in meaning (+ adds, == compares, << prints) still feel natural for the class? For Money, + meaningfully matches addition and == meaningfully matches equality; but, for example, Money operator*(const Money& a, const Money& b) could be left undefined, because multiplying two amounts has no monetary meaning — this lesson does not write such an operator.
Violating this limit compiles, but it makes the code unreadable. C++ enforces no rule about which meaning an operator must be used with — technically, a subtraction could be written into the body of operator+, and the compiler would not object. But such code separates what everyone who writes a + b expects (“it is adding”) from what actually happens (“it is subtracting”) — this is a design flaw that misleads the class’s user, and its error cannot be caught at compile time. The only thing that limits the power of overloading is not the compiler, but the discipline of staying faithful to the expectation an operator raises in the reader.
Where explicit, the Copy Constructor, and Operators Meet
Money’s constructor is defined as explicit Money(long cents) — the explicit rule from lesson 03 applies here too: an implicit conversion like Money m = 100; does not compile, and Money m(100); or Money m{100}; must be written instead. This interacts directly with operator overloading: the line return Money(cents_ + other.cents_); inside operator+’s body writes an explicit construction; if the constructor were not explicit and the body wrote return cents_ + other.cents_;, the long result could be implicitly converted into a Money — this would open the door to exactly the kind of unwanted implicit conversion chain lesson 03 warned about (for example, a Money being compared, indirectly and by mistake, with a long). operator+’s result is always returned by explicitly constructing a Money; this both follows the explicit rule and keeps what the operator produces explicit in the code. The copy constructor from lesson 05 also comes into play here implicitly: when the line return Money(...) returns a new Money value, the compiler, in most cases, constructs this value directly into the place the caller will receive it (an optimization that skips the value copy), but the conceptual model is still “a new object was constructed and returned” — the copy constructor never being called is an optimization detail, and it does not change the fact that operator+ produces a new object.
Value Semantics: This Course’s Context
M11/K01 showed that a struct is copied field by field on assignment; this is value semantics: when a value is assigned or passed, the two sides become independent of each other. Money applies this principle with C++’s own tools — the copy constructor, copy assignment, and now operator+ returning a new value. operator+=, by contrast, deliberately steps outside this principle: it modifies its own object, because +=‘s built-in meaning is already “update the value on the left” (x += 1 also modifies x, it does not construct a new variable). Which of these two behavior patterns is chosen when writing an operator for a class must stay consistent with the behavior of the operator’s built-in int/long counterpart — Money preserves this consistency: + produces a new value, += modifies the existing one, exactly as for int.
Summary of the Copy, Temporary, and Self Distinction
This topic’s seven lessons measured a single question from different angles: which object does an expression work with? Lessons 03 and 04 measured how an object acquires its own resource (constructor, destructor, RAII); lesson 05 measured how a copy separates from its original (copy); lesson 06 measured how a reference works with the caller’s self without any copying; and this lesson measured when an operator produces a new temporary, and when it modifies its own object and returns itself. The four kinds — self, copy, part, temporary — correspond to the single question to ask when reading a C++ expression: which other object is the object this expression produces or operates on the same as, and which is it different from?
The Bounding Measurement
// chain-measurement.cpp — how many temporaries does a + b + c produce
#include <cstdio>
#include "money.hpp"
int main() {
Money a(10);
Money b(20);
Money c(30);
Money total = a + b + c;
use_money(total);
return 0;
}
cents=60
CR17. The expression a + b + c is evaluated left to right: first a + b runs and produces an unnamed Money with the value cents_=30; then this result becomes the left side of the + c call. Because operator+ is a member function, the second call’s this must point to an object: for this reason, the result of a + b is constructed as one temporary and lives until the full expression (the end of a + b + c;, the semicolon) finishes, then is destroyed. The second operator+ call’s result, with the value cents_=60, is not constructed as a separate temporary: under C++17’s guaranteed copy elision, it is constructed directly in total’s own storage. Two operator+ calls therefore produce one temporary; total is the very result of the second call itself, not a copy of it. Because Money‘s destructor does not contain a printf here, the moment the temporary is constructed and destroyed is not observable in this block; the count is read from the language’s rule, not from this measurement. The answer to the question of “how many temporaries” a chained addition produces depends not so much on how many times the operator is called, but on where each result goes: an intermediate result that becomes the object of the next call is a temporary, while a final result that initializes a variable is that variable itself.
Summary
- The expression this lesson measures is of kind temporary:
operator+returns a new, unnamedMoneywithout modifying the operands; its return type is by value (Money, not a reference). operator+=modifies its own object and returns itself (*this) by reference; its return type isMoney&.- Binary arithmetic operators are usually written as free functions;
<<cannot be a member function because its left side is not the class itself. - An operator should be defined only when its built-in meaning is natural for the class.
- In a chained expression (
a + b + c), only the intermediate result (a + b) is constructed as a temporary and lives until the full expression finishes; the final result is constructed directly insidetotal. - This topic’s seven lessons asked the same question: does a C++ expression work with a self, copy, part, or temporary object?
Next Step
This topic measured how an object acquires its own resource, is copied, is passed by reference, and produces new objects through operators. The next topic moves away from this single-object model to the relationship between classes — how a class extends another class’s behavior, and how it is determined at run time which function gets called.
To keep your progress and take notes, Log in
My notes
Log in to take notes.