Giao diện
📜 Rule of 5 — Special Member Functions
Nếu class quản lý resource, bạn cần định nghĩa 5 special member functions hoặc = default / = delete tất cả.
The 5 Special Members
cpp
class Resource {
public:
// 1. Destructor
~Resource();
// 2. Copy Constructor
Resource(const Resource& other);
// 3. Copy Assignment
Resource& operator=(const Resource& other);
// 4. Move Constructor
Resource(Resource&& other) noexcept;
// 5. Move Assignment
Resource& operator=(Resource&& other) noexcept;
};Analogy: Hợp đồng chuyển nhượng
┌─────────────────────────────────────────────────────────────────┐
│ CONTRACT ANALOGY │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Class = Công ty sở hữu tài sản │
│ │
│ 1. Destructor = Giải thể công ty │
│ → Phải thanh lý tài sản │
│ │
│ 2. Copy Constructor = Thành lập công ty mới từ mẫu │
│ → Sao chép tất cả tài sản │
│ │
│ 3. Copy Assignment = Sáp nhập công ty │
│ → Hủy tài sản cũ, sao chép tài sản mới │
│ │
│ 4. Move Constructor = Thành lập mới, chuyển nhượng tài sản │
│ → Công ty cũ rỗng, công ty mới có tất cả │
│ │
│ 5. Move Assignment = Tiếp quản hoàn toàn │
│ → Hủy của mình, lấy của họ, họ rỗng │
│ │
└─────────────────────────────────────────────────────────────────┘Why Rule of 5?
If your class manages a resource (memory, file handle, socket, etc.), the compiler-generated defaults WON'T work correctly.
cpp
// ❌ BAD: Uses default copy (shallow copy)
class BadBuffer {
int* data_;
size_t size_;
public:
BadBuffer(size_t size) : data_(new int[size]), size_(size) {}
~BadBuffer() { delete[] data_; }
// Compiler generates shallow copy!
};
int main() {
BadBuffer b1(100);
BadBuffer b2 = b1; // Shallow copy: b2.data_ == b1.data_!
} // 💥 Double delete! b1 and b2 both delete same memory!Complete Example
cpp
#include <algorithm> // std::copy
#include <utility> // std::exchange
class Buffer {
int* data_;
size_t size_;
public:
// Constructor
explicit Buffer(size_t size)
: data_(new int[size])
, size_(size)
{}
// 1. Destructor
~Buffer() {
delete[] data_;
}
// 2. Copy Constructor (deep copy)
Buffer(const Buffer& other)
: data_(new int[other.size_])
, size_(other.size_)
{
std::copy(other.data_, other.data_ + size_, data_);
}
// 3. Copy Assignment
Buffer& operator=(const Buffer& other) {
if (this != &other) { // Self-assignment check
delete[] data_; // Free old resource
size_ = other.size_;
data_ = new int[size_];
std::copy(other.data_, other.data_ + size_, data_);
}
return *this;
}
// 4. Move Constructor
Buffer(Buffer&& other) noexcept
: data_(std::exchange(other.data_, nullptr))
, size_(std::exchange(other.size_, 0))
{}
// 5. Move Assignment
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data_; // Free old resource
data_ = std::exchange(other.data_, nullptr);
size_ = std::exchange(other.size_, 0);
}
return *this;
}
// Accessors
size_t size() const { return size_; }
int& operator[](size_t i) { return data_[i]; }
};Copy vs Move
┌─────────────────────────────────────────────────────────────────┐
│ COPY vs MOVE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ COPY (expensive) MOVE (cheap) │
│ ──────────────── ──────────── │
│ │
│ Source Dest Source Dest │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ ABC │ ───► │ ABC │ │ ABC │ ───► │ ABC │ │
│ └─────┘ └─────┘ └──┬──┘ └──┬──┘ │
│ ↓ ↓ ↓ swap ↓ │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ ABC │ │ ABC │ │null │ │ ABC │ │
│ └─────┘ └─────┘ └─────┘ └─────┘ │
│ │
│ Duplicates data Just transfers pointer │
│ O(n) for n bytes O(1) constant time │
│ │
└─────────────────────────────────────────────────────────────────┘Copy-and-Swap Idiom
A elegant, exception-safe way to implement assignment:
cpp
class Buffer {
// ... same as before ...
// Single unified assignment operator
Buffer& operator=(Buffer other) noexcept { // By value!
swap(*this, other);
return *this;
} // other destroyed here, cleans up old data
friend void swap(Buffer& a, Buffer& b) noexcept {
using std::swap;
swap(a.data_, b.data_);
swap(a.size_, b.size_);
}
};Rule of Zero
💡 BEST PRACTICE
Rule of Zero: If your class doesn't manage resources directly, don't define any of the 5.
Use smart pointers instead!
cpp
// ✅ Rule of Zero - use smart pointers
class ModernBuffer {
std::unique_ptr<int[]> data_;
size_t size_;
public:
explicit ModernBuffer(size_t size)
: data_(std::make_unique<int[]>(size))
, size_(size)
{}
// No destructor needed - unique_ptr handles it
// No copy constructor - unique_ptr is move-only
// No copy assignment - unique_ptr is move-only
// Move constructor - compiler generates correctly!
// Move assignment - compiler generates correctly!
};Rule of 5 Defaults
cpp
class Widget {
public:
// Explicitly use compiler defaults
~Widget() = default;
Widget(const Widget&) = default;
Widget& operator=(const Widget&) = default;
Widget(Widget&&) = default;
Widget& operator=(Widget&&) = default;
};Disable Operations
cpp
class NonCopyable {
public:
NonCopyable() = default;
~NonCopyable() = default;
// Disable copy
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
// Enable move
NonCopyable(NonCopyable&&) = default;
NonCopyable& operator=(NonCopyable&&) = default;
};Summary Table
| Function | When Called |
|---|---|
| Destructor | Object goes out of scope |
| Copy Constructor | T obj2(obj1); or T obj2 = obj1; |
| Copy Assignment | obj2 = obj1; (obj2 exists) |
| Move Constructor | T obj2(std::move(obj1)); |
| Move Assignment | obj2 = std::move(obj1); |
📚 Tổng kết
| Rule | Description |
|---|---|
| Rule of 5 | Define all 5 or none |
| Rule of Zero | Use smart pointers, define none |
| noexcept | Mark move operations noexcept |
| Self-assignment | Check this != &other |
🎯 Module Complete!
Bạn đã hoàn thành Module 9: Memory Management:
- ✅ new/delete dangers
- ✅ RAII principle
- ✅ unique_ptr (default choice)
- ✅ shared_ptr (shared ownership)
- ✅ weak_ptr (breaking cycles)
- ✅ Rule of 5
Welcome to Modern C++! 🧠