Quy tắc Ba: Nếu một class cần định nghĩa rõ ràng **một trong ba** hàm sau, thường thì nó cần định nghĩa **cả ba**:
- Copy Constructor -
Class(const Class&) - Copy Assignment Operator -
Class& operator=(const Class&) - Destructor -
~Class()
Tại sao?
Class quản lý tài nguyên động (con trỏ, file handle, socket...). Bản sao mặc định (shallow copy) chỉ copy giá trị con trỏ → hai đối tượng cùng trỏ một vùng nhớ → double free, memory leak, dữ liệu sai.
Ví dụ vi phạm Rule of Three
class Bad {
int* data;
public:
Bad(int sz) : data(new int[sz]) {}
// Thiếu: copy ctor, assignment, dtor
};
int main() {
Bad a(10);
Bad b = a; // Shallow copy: cả hai trỏ cùng mảng
} // Double free khi a và b hủy
Ví dụ tuân thủ Rule of Three (C++98)
class Good {
int* data; size_t sz;
public:
Good(size_t n) : sz(n), data(new int[n]) {}
~Good() { delete[] data; }
// Copy constructor - Deep copy
Good(const Good& other) : sz(other.sz), data(new int[other.sz]) {
copy(other.data, other.data+sz, data);
}
// Copy assignment - Copy-and-swap idiom
Good& operator=(Good other) { // pass by value -> copy
swap(sz, other.sz);
swap(data, other.data);
return *this;
} // other hủy, giải phóng data cũ
};
C++11: Rule of Five
Thêm **Move Constructor** và **Move Assignment** để tối ưu hiệu năng:
Good(Good&& other) noexcept : data(other.data), sz(other.sz) {
other.data = nullptr; other.sz = 0;
}
Good& operator=(Good&& other) noexcept {
delete[] data;
data = other.data; sz = other.sz;
other.data = nullptr; other.sz = 0;
return *this;
}
C++11/14/17: Rule of Zero (Khuyên dùng)
Sử dụng RAII containers (vector, unique_ptr, shared_ptr) để **không cần viết** bất kỳ hàm nào trong Rule of Three/Five. Compiler-generated sẽ đúng.
class Modern {
vector<int> data; // Tự quản lý bộ nhớ
public:
Modern(size_t n) : data(n) {}
// Không cần dtor, copy, move - compiler làm đúng!
};