8.4 Operator Overloading
Operator overloading lets your own types work with familiar symbols such as +, ==, <, [], and <<. It is one of the features that makes C++ feel expressive, but it also requires discipline. An overloaded operator should mean what readers already expect that operator to mean.
For example, Fraction + Fraction is clear: it creates a new sum. Student + Student is probably unclear. Good C++ operators make code shorter without making it more mysterious.
Arithmetic operators
For value types, operator+ usually returns a new object and does not modify either operand.
class Fraction {
private:
int numerator_;
int denominator_;
public:
Fraction(int numerator, int denominator)
: numerator_(numerator), denominator_(denominator) {}
Fraction operator+(const Fraction& other) const {
int n = numerator_ * other.denominator_ + other.numerator_ * denominator_;
int d = denominator_ * other.denominator_;
return Fraction(n, d);
}
};The left operand is *this; the right operand is other.
Fraction half(1, 2);
Fraction third(1, 3);
Fraction sum = half + third; // calls half.operator+(third)The const after the function means half + third does not change half. If you want a modifying operator, use operator+=.
Fraction& operator+=(const Fraction& other) {
numerator_ = numerator_ * other.denominator_ + other.numerator_ * denominator_;
denominator_ = denominator_ * other.denominator_;
return *this;
}Returning *this by reference is what makes a += b += c possible.
Stream operators
operator<< is usually a non-member function because the left operand is the stream, not your object.
std::cout << value;The compiler reads that as:
operator<<(std::cout, value);For private members, the stream operator is often declared as a friend.
class Fraction {
private:
int numerator_;
int denominator_;
public:
Fraction(int numerator, int denominator)
: numerator_(numerator), denominator_(denominator) {}
friend std::ostream& operator<<(std::ostream& out, const Fraction& value);
};
std::ostream& operator<<(std::ostream& out, const Fraction& value) {
out << value.numerator_ << "/" << value.denominator_;
return out;
}Returning std::ostream& is essential. It returns the same stream so another << can continue the chain:
std::cout << half << " + " << third << " = " << sum << std::endl;Comparison operators
Comparison operators should answer clear questions. operator== asks whether two values are equal. operator< asks whether the left value is ordered before the right value.
class Money {
private:
int cents_;
public:
explicit Money(int cents) : cents_(cents) {}
bool operator==(const Money& other) const {
return cents_ == other.cents_;
}
bool operator<(const Money& other) const {
return cents_ < other.cents_;
}
};These functions are marked const because comparing should not change either object. They return bool because comparisons are questions. Once a type has meaningful comparisons, it becomes easier to sort, search, and test values.
Keep overloaded operators unsurprising:
+should not print.==should not change state.<<should write to the stream and return the stream.- If an operator's meaning is hard to explain in one sentence, use a named member function instead.