Operator Overloading & Rule of Three/Five
অপারেটর ওভারলোডিং ও Rule of Three/Five
1. Make Your Type Feel Built-in
Operator overloading lets you write a + b for your type. Use it for value-like types (vectors, fractions, complex numbers).
#include <iostream>
struct Vec2 {
double x, y;
Vec2 operator+(const Vec2& o) const { return {x + o.x, y + o.y}; }
Vec2 operator-(const Vec2& o) const { return {x - o.x, y - o.y}; }
Vec2 operator*(double s) const { return {x * s, y * s}; }
bool operator==(const Vec2&) const = default; // C++20
};
int main() {
Vec2 a{1, 2}, b{3, 4};
Vec2 c = a + b;
std::cout << c.x << ", " << c.y << "\n";
}
2. operator<< for Streams
Stream output overloads as a free function (must take ostream by reference):
#include <iostream>
struct Vec2 { double x, y; };
std::ostream& operator<<(std::ostream& os, const Vec2& v) {
return os << "(" << v.x << ", " << v.y << ")";
}
int main() {
Vec2 v{3, 4};
std::cout << v << "\n";
}
3. The Rule of Three / Five / Zero
| Rule | What it says |
|---|---|
| Rule of Three | If you write any of {dtor, copy ctor, copy assign}, write all three. |
| Rule of Five | Adds move ctor and move assign — write all five for move-aware classes. |
| Rule of Zero | Don't write any of them. Use members that manage themselves (smart pointers, vectors, strings). |
4. The Big Five Example
class Buffer {
char* data_;
size_t n_;
public:
Buffer(size_t n) : data_(new char[n]), n_(n) {}
~Buffer() { delete[] data_; }
// Copy
Buffer(const Buffer& o) : data_(new char[o.n_]), n_(o.n_) {
std::copy(o.data_, o.data_ + n_, data_);
}
Buffer& operator=(const Buffer& o) {
if (this != &o) {
delete[] data_;
n_ = o.n_;
data_ = new char[n_];
std::copy(o.data_, o.data_ + n_, data_);
}
return *this;
}
// Move (noexcept!)
Buffer(Buffer&& o) noexcept : data_(o.data_), n_(o.n_) {
o.data_ = nullptr; o.n_ = 0;
}
Buffer& operator=(Buffer&& o) noexcept {
if (this != &o) {
delete[] data_;
data_ = o.data_; n_ = o.n_;
o.data_ = nullptr; o.n_ = 0;
}
return *this;
}
};
(In real life: just use std::vector<char> and write nothing!)
5. =default and =delete
= default: ask compiler to generate the standard implementation.= delete: forbid that operation entirely.
Make a class non-copyable: X(const X&) = delete; X& operator=(const X&) = delete;
6. Practice Problems
- Overload
+for two Fractions.✨ Show Answer
Fraction operator+(const Fraction& o) const { return {num_*o.denom_ + o.num_*denom_, denom_*o.denom_}; } - When should you make a class non-copyable?
✨ Show Answer
When the resource it owns can't be sensibly copied — file handles, mutexes, sockets, unique_ptr-like ownership.
- What's the Rule of Zero?
✨ Show Answer
Design classes such that you don't need to write any of the special members. Compose with self-managing types. Compiler defaults work correctly.
- Define
operator<<for printing a Date.✨ Show Answer
std::ostream& operator<<(std::ostream& os, const Date& d) { return os << d.year << "-" << d.month << "-" << d.day; } - Why is move assignment marked
noexcept?✨ Show Answer
STL containers (e.g. vector) only use move during reallocation if move is noexcept. Otherwise they copy (slower) for strong exception safety.
- Why default the comparison with
= default?✨ Show Answer
C++20 generates element-wise comparison automatically — saves boilerplate, reduces bugs.
- What does
X(const X&) = deletemean?✨ Show Answer
The class is non-copyable. Any attempt to copy fails to compile.
- Overload prefix and postfix
++.✨ Show Answer
X& operator++() { ++n_; return *this; } // prefix X operator++(int) { X t = *this; ++n_; return t; } // postfix (dummy int) - Why must
operator=handle self-assignment?✨ Show Answer
x = xcan happen. If you naively delete then copy, you delete what you're trying to copy. Checkthis != &oor use copy-and-swap. - Show copy-and-swap idiom.
✨ Show Answer
Buffer& operator=(Buffer o) // pass by value: caller copies/moves { swap(*this, o); return *this; } // o destroyed automatically with old dataSelf-assignment-safe and exception-safe.
- When does the compiler NOT auto-generate the move ctor?
✨ Show Answer
If you've declared any of: copy ctor, copy assign, move assign, destructor. Then it disables the default move. Use
= defaultto bring it back. - What does
std::move(x)do?✨ Show Answer
Casts
xto an rvalue reference, telling the compiler "you may steal from x". Doesn't actually move anything; the move ctor / assign operator does the work. - Overload
operator[].✨ Show Answer
int& operator[](size_t i) { return data_[i]; } const int& operator[](size_t i) const { return data_[i]; } - Define a class member function as deleted to forbid an overload.
✨ Show Answer
class X { public: void f(int); void f(double) = delete; // forbid double conversion };
Summary
Operator overloading makes user types feel native. Rule of Five reminds you to handle all
five special functions when one is needed. Rule of Zero says: design so you don't need to
write any. Mark moves noexcept. Use = default / = delete instead of
hand-writing trivial members.