8 Operator Overloading and Friend Functions
9 Operator Overloading and Friend Functions
Week 3, Session 2. CSS 342.
9.1 Warmup
Palindrome Number (LeetCode #9) — revisit. This time write operator== for a custom IntPair class and use it to compare the front and back digits.
9.2 Learning objectives
- Overload comparison operators (
==,!=,<,>,<=,>=) to enable objects to be sorted and compared. - Implement
operator<<as afriendfunction to integrate a class withstd::cout. - Implement
operator>>to enablestd::cininput into a class. - Explain the difference between member and non-member operator overloading and choose correctly for
<</>>. - Simplify boolean return patterns (replace
if (x) return true; else return false;withreturn x).
9.3 Why overload operators?
So your class participates in the language. sort, set, map, cout — they all use operators. If your class defines operator<, you can put it in a set. If your class defines operator<<, you can print it with cout. The point is uniform syntax: your type behaves like a built-in.
9.4 Comparison operators
A Box class with width and height. We want == (same dimensions) and < (smaller area).
class Box {
public:
Box(int w, int h) : width_(w), height_(h) {}
int area() const { return width_ * height_; }
bool operator==(const Box& other) const {
return width_ == other.width_ && height_ == other.height_;
}
bool operator<(const Box& other) const {
return area() < other.area();
}
private:
int width_;
int height_;
};Now:
Box a(2, 3);
Box b(3, 2);
Box c(5, 5);
cout << (a == b) << endl; // 0 (different dims, same area)
cout << (a < c) << endl; // 1
set<Box> boxes; // works! set needs operator<
boxes.insert(a);
boxes.insert(c);Once you have == and <, the others (!=, >, <=, >=) follow logically. In C++20 you can use auto operator<=>(const Box&) const = default; and get all six for free. We are sticking with C++17 in this book, so write them explicitly when you need them.
9.5 operator<< — printing
This is the most useful one. It lets you cout << myBox.
// In Box.h, declare as friend
class Box {
friend ostream& operator<<(ostream& os, const Box& b);
// ... rest of class
};
// In Box.cpp, define as non-member
ostream& operator<<(ostream& os, const Box& b) {
os << "<Box " << b.width_ << "x" << b.height_ << ">";
return os;
}Why non-member? Because the left operand is ostream, not Box. We cannot add member functions to ostream — it is not our class. So operator<< must be a free function. The friend declaration just gives it access to private fields.
Why return ostream&? Because of chaining:
cout << "Box a: " << a << " has area " << a.area() << endl;Each << returns its ostream so the next << can use it.
9.6 operator>> — reading
Symmetric to << but for input:
istream& operator>>(istream& is, Box& b) {
int w, h;
is >> w >> h;
b = Box(w, h); // or set fields directly if friend
return is;
}Used as:
Box b(0, 0);
cin >> b; // reads two ints into the box9.7 Member vs. non-member — the rule
| Operator | Member or non-member? | Why |
|---|---|---|
+, -, *, / |
Either, often member | Symmetric — left operand is your class |
+=, -=, *= |
Always member | Modifies left operand |
==, <, > |
Either, often member | Comparison |
<<, >> |
Non-member (friend) | Left operand is stream |
= (assignment) |
Always member | Implicit, must be member |
[] |
Always member | Indexing on self |
When in doubt: member if the left operand is your class, non-member otherwise.
9.8 Compound assignment
class Fraction {
public:
Fraction(int n, int d) : num_(n), den_(d) {}
Fraction& operator+=(const Fraction& other) {
num_ = num_ * other.den_ + other.num_ * den_;
den_ = den_ * other.den_;
return *this; // return ref to self for chaining
}
// operator+ can be implemented in terms of +=
Fraction operator+(const Fraction& other) const {
Fraction result = *this;
result += other;
return result;
}
private:
int num_, den_;
};That return *this; is critical. *this is the current object (dereferenced from this, which is a pointer). Returning a reference allows chains like a += b += c.
9.9 Style: simplify boolean returns
A pattern I see in P1 feedback that needs to die:
// Don't write this:
bool isEmpty() const {
if (size_ == 0) {
return true;
} else {
return false;
}
}
// Write this:
bool isEmpty() const {
return size_ == 0;
}If the thing you want to return is already a bool, return it. Wrapping it in if/else true/false is a tell that you have not internalized that == produces a boolean.
The same applies to:
// Bad
return x != y ? true : false;
// Good
return x != y;9.10 Try it
Add operator< to Fraction so that fractions can be sorted in ascending order. Hint: avoid floating point. Cross-multiply: a/b < c/d is equivalent to a*d < c*b (when b and d are positive).
bool Fraction::operator<(const Fraction& other) const {
return num_ * other.den_ < other.num_ * den_;
}Test it by inserting a few fractions into set<Fraction> and iterating.
9.11 Self-check
operator<< typically a non-member friend function?ostream. A non-member free function with friend access is the idiom.
if (n > 0) { return true; } else { return false; }:bool; wrapping it in if/else is noise.
9.12 Challenges
- Add
operator<<andoperator<to yourFractionclass. Sort avector<Fraction>and print. - Design HashMap (#706) — class design with operators for clear use.
- Build a
Moneyclass withoperator+,operator-,operator*(by an int), andoperator<<.
9.13 Where this fits
You can now make your class behave like a value type. Next: what happens when those values are copied.
| You are here | Coming up |
|---|---|
| Operator overloading, friends, stream operators, member vs. non-member | Chapter 7: copy constructors and the Rule of Three |