Skip to content

🔒 RAII — The Heart of C++

RAII = Resource Acquisition Is Initialization. Tài nguyên được tự động giải phóng khi object ra khỏi scope.

Analogy: Khóa cửa tự động

┌─────────────────────────────────────────────────────────────────┐
│                    AUTO-LOCKING DOOR ANALOGY                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   Manual Lock (new/delete):                                     │
│   ─────────────────────────                                     │
│   1. Mở cửa                                                     │
│   2. Vào phòng                                                  │
│   3. Làm việc                                                   │
│   4. NHỚ khóa cửa khi ra ← Có thể quên!                        │
│                                                                 │
│   Auto-Lock (RAII):                                             │
│   ─────────────────                                             │
│   1. Quẹt thẻ vào phòng (acquire)                              │
│   2. Làm việc                                                   │
│   3. Ra khỏi phòng → Cửa TỰ ĐỘNG khóa                          │
│                                                                 │
│   RAII = Cửa tự động khóa khi bạn rời đi                       │
│   Không thể quên, không thể sai!                               │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

RAII Principle

cpp
class Resource {
public:
    // Constructor: ACQUIRE resource
    Resource() {
        // Open file, allocate memory, lock mutex, etc.
    }
    
    // Destructor: RELEASE resource (automatic!)
    ~Resource() {
        // Close file, free memory, unlock mutex, etc.
    }
};

void useResource() {
    Resource r;  // Constructor called → resource acquired
    
    // Use resource...
    doSomething(r);
    mayThrowException();  // Even if exception!
    
}  // ← Destructor called → resource released AUTOMATICALLY

Example: File Handle

Without RAII

cpp
void readFile_bad(const char* filename) {
    FILE* file = fopen(filename, "r");
    if (!file) return;
    
    char buffer[1024];
    while (fgets(buffer, sizeof(buffer), file)) {
        if (shouldStop(buffer)) {
            // Oops! Forgot to close file!
            return;
        }
        process(buffer);
    }
    
    fclose(file);  // Only reaches here if no early return
}

With RAII

cpp
class FileHandle {
    FILE* file_;
    
public:
    explicit FileHandle(const char* filename) 
        : file_(fopen(filename, "r")) {}
    
    ~FileHandle() {
        if (file_) fclose(file_);  // Auto close!
    }
    
    FILE* get() const { return file_; }
    
    // Prevent copying
    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;
};

void readFile_good(const char* filename) {
    FileHandle file(filename);  // Open in constructor
    
    if (!file.get()) return;
    
    char buffer[1024];
    while (fgets(buffer, sizeof(buffer), file.get())) {
        if (shouldStop(buffer)) {
            return;  // ✅ File auto-closed by destructor!
        }
        process(buffer);
    }
}  // ← Destructor closes file automatically

Example: Lock Guard

cpp
#include <mutex>

std::mutex mtx;
int sharedData = 0;

// ❌ Manual lock - dangerous
void increment_bad() {
    mtx.lock();
    
    sharedData++;
    
    if (sharedData > 100) {
        return;  // 💀 Forgot to unlock!
    }
    
    mtx.unlock();
}

// ✅ RAII lock - safe
void increment_good() {
    std::lock_guard<std::mutex> lock(mtx);  // Lock acquired
    
    sharedData++;
    
    if (sharedData > 100) {
        return;  // ✅ Auto unlock on return!
    }
    
}  // ← Auto unlock on scope exit

RAII Guarantees

┌─────────────────────────────────────────────────────────────────┐
│                    RAII GUARANTEES                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   ✅ Normal exit → Destructor called                           │
│   ✅ Return early → Destructor called                          │
│   ✅ Exception thrown → Destructor called                       │
│   ✅ Break/continue → Destructor called                        │
│                                                                 │
│   The ONLY way to skip destructor:                              │
│   ❌ std::terminate()                                           │
│   ❌ std::abort()                                               │
│   ❌ std::_Exit()                                               │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Standard Library RAII Classes

ResourceRAII Wrapper
Memorystd::unique_ptr, std::shared_ptr
Mutexstd::lock_guard, std::unique_lock
Filestd::fstream
Threadstd::jthread (C++20)

Custom RAII Class Template

cpp
template<typename T, typename Deleter>
class ScopeGuard {
    T resource_;
    Deleter deleter_;
    bool released_ = false;
    
public:
    ScopeGuard(T resource, Deleter deleter)
        : resource_(resource), deleter_(deleter) {}
    
    ~ScopeGuard() {
        if (!released_) {
            deleter_(resource_);
        }
    }
    
    T get() const { return resource_; }
    
    void release() { released_ = true; }
    
    // Non-copyable
    ScopeGuard(const ScopeGuard&) = delete;
    ScopeGuard& operator=(const ScopeGuard&) = delete;
};

// Usage
void example() {
    auto* handle = acquireHandle();
    ScopeGuard guard(handle, [](auto* h) { releaseHandle(h); });
    
    // Use handle...
    // Auto-released when guard goes out of scope
}

📚 Tổng kết

AspectDescription
AcquireIn constructor
ReleaseIn destructor (automatic!)
Exception safeYes, always
Never forgetCompiler guarantees cleanup

➡️ Tiếp theo

RAII cho memory = Smart Pointers. Bắt đầu với lựa chọn mặc định:

unique_ptr → — Exclusive ownership.

🧠 Quiz

Câu 1: RAII (Resource Acquisition Is Initialization) hoạt động dựa trên cơ chế nào của C++?

  • [ ] A) Garbage collector tự động thu gom bộ nhớ
  • [x] B) Destructor được tự động gọi khi object ra khỏi scope
  • [ ] C) Exception handler tự động giải phóng tài nguyên
  • [ ] D) Compiler tự thêm code giải phóng tài nguyên

💡 Giải thích: RAII dựa vào deterministic destruction của C++ — destructor luôn được gọi khi object ra khỏi scope, kể cả khi có exception. Constructor acquire resource, destructor release resource. Không có garbage collector — mọi thứ xác định và có thể dự đoán.

Câu 2: Đoạn code nào thể hiện RAII pattern đúng?

  • [ ] A) FILE* f = fopen("data.txt", "r"); /* ... */ fclose(f);
  • [x] B) std::ifstream file("data.txt"); /* ... */ // auto-closed
  • [ ] C) int* p = new int(42); /* ... */ delete p;
  • [ ] D) malloc(100); /* ... */ free(ptr);

💡 Giải thích: std::ifstream là RAII wrapper cho file handle — constructor mở file, destructor tự động đóng file khi ra khỏi scope. Các lựa chọn khác đều là manual resource management — phải nhớ gọi fclose/delete/free, dễ quên và không exception-safe.

Câu 3: Tại sao RAII đảm bảo exception safety?

  • [ ] A) RAII ngăn exception không xảy ra
  • [ ] B) RAII tự động bắt và xử lý mọi exception
  • [x] C) Khi exception xảy ra, stack unwinding gọi destructor của tất cả local objects
  • [ ] D) RAII chuyển exception thành error code

💡 Giải thích: Khi exception được throw, C++ thực hiện stack unwinding — gọi destructor của tất cả local objects trên stack theo thứ tự ngược. Nhờ đó, RAII objects luôn giải phóng resource đúng cách, dù code path bình thường hay exception path.