S SmartDocs
Série: C++ cpp 361 linhas · Atualizado 2026-04-03

raii.cpp

C++/Part2_物件導向/Ch09_建構子與解構子/raii.cpp

// ============================================================
// Ch09 — RAII(Resource Acquisition Is Initialization)
//   資源取得即初始化 — C++ 最重要的慣用手法之一
// 編譯:g++ -std=c++17 -Wall -o raii raii.cpp
// ============================================================

#include <iostream>
#include <string>
#include <fstream>
#include <stdexcept>
#include <memory>

// ============================================================
// 1. FileGuard — 檔案資源的 RAII 包裝
//    建構子開檔,解構子關檔
// ============================================================

class FileGuard {
private:
    std::ofstream file;
    std::string filename;

public:
    // 建構子:取得資源(開檔)
    explicit FileGuard(const std::string& filename)
        : file(filename), filename(filename) {
        if (!file.is_open()) {
            throw std::runtime_error("無法開啟檔案: " + filename);
        }
        std::cout << "    [FileGuard] 開啟檔案: " << filename << std::endl;
    }

    // 解構子:釋放資源(關檔)
    ~FileGuard() {
        if (file.is_open()) {
            file.close();
            std::cout << "    [FileGuard] 關閉檔案: " << filename << std::endl;
        }
    }

    // 禁止拷貝(檔案 handle 不應被複製)
    FileGuard(const FileGuard&) = delete;
    FileGuard& operator=(const FileGuard&) = delete;

    // 寫入資料
    void write(const std::string& content) {
        file << content;
        std::cout << "    [FileGuard] 寫入: " << content;
    }

    void writeLine(const std::string& content) {
        file << content << "\n";
        std::cout << "    [FileGuard] 寫入行: " << content << std::endl;
    }
};

// ============================================================
// 2. MemoryBlock — 記憶體資源的 RAII 包裝
//    模擬簡單的動態記憶體管理
// ============================================================

class MemoryBlock {
private:
    int* data;
    int size;
    std::string name;

public:
    // 建構子:取得資源(配置記憶體)
    MemoryBlock(const std::string& name, int size)
        : data(new int[size]), size(size), name(name) {
        std::fill(data, data + size, 0);
        std::cout << "    [MemoryBlock] " << name
                  << " 配置 " << size << " 個 int ("
                  << size * sizeof(int) << " bytes)" << std::endl;
    }

    // 解構子:釋放資源
    ~MemoryBlock() {
        std::cout << "    [MemoryBlock] " << name
                  << " 釋放記憶體" << std::endl;
        delete[] data;
    }

    // 禁止拷貝
    MemoryBlock(const MemoryBlock&) = delete;
    MemoryBlock& operator=(const MemoryBlock&) = delete;

    int& operator[](int index) {
        if (index < 0 || index >= size) {
            throw std::out_of_range("MemoryBlock: 索引超出範圍");
        }
        return data[index];
    }

    int operator[](int index) const {
        if (index < 0 || index >= size) {
            throw std::out_of_range("MemoryBlock: 索引超出範圍");
        }
        return data[index];
    }

    int getSize() const { return size; }

    void print() const {
        std::cout << "    [" << name << "] = [";
        for (int i = 0; i < size; ++i) {
            if (i > 0) std::cout << ", ";
            std::cout << data[i];
        }
        std::cout << "]" << std::endl;
    }
};

// ============================================================
// 3. LockGuard — 模擬鎖的 RAII 包裝
//    (簡化版,展示概念)
// ============================================================

class SimpleMutex {
private:
    bool locked = false;
    std::string name;

public:
    explicit SimpleMutex(const std::string& name) : name(name) {}

    void lock() {
        locked = true;
        std::cout << "    [Mutex:" << name << "] 🔒 已上鎖" << std::endl;
    }

    void unlock() {
        locked = false;
        std::cout << "    [Mutex:" << name << "] 🔓 已解鎖" << std::endl;
    }

    bool isLocked() const { return locked; }
};

// RAII 鎖管理器 — 類似 std::lock_guard
class LockGuard {
private:
    SimpleMutex& mutex;

public:
    // 建構子:取得鎖
    explicit LockGuard(SimpleMutex& m) : mutex(m) {
        mutex.lock();
    }

    // 解構子:釋放鎖
    ~LockGuard() {
        mutex.unlock();
    }

    // 禁止拷貝與賦值
    LockGuard(const LockGuard&) = delete;
    LockGuard& operator=(const LockGuard&) = delete;
};

// ============================================================
// 4. SimpleUniquePtr — 簡易智慧指標
//    展示 unique_ptr 的核心 RAII 概念
// ============================================================

template <typename T>
class SimpleUniquePtr {
private:
    T* ptr;

public:
    // 建構子:取得擁有權
    explicit SimpleUniquePtr(T* p = nullptr) : ptr(p) {
        if (ptr) {
            std::cout << "    [SimpleUniquePtr] 取得資源擁有權" << std::endl;
        }
    }

    // 解構子:釋放資源
    ~SimpleUniquePtr() {
        if (ptr) {
            std::cout << "    [SimpleUniquePtr] 自動釋放資源" << std::endl;
            delete ptr;
        }
    }

    // 禁止拷貝
    SimpleUniquePtr(const SimpleUniquePtr&) = delete;
    SimpleUniquePtr& operator=(const SimpleUniquePtr&) = delete;

    // 允許移動
    SimpleUniquePtr(SimpleUniquePtr&& other) noexcept : ptr(other.ptr) {
        other.ptr = nullptr;
    }

    SimpleUniquePtr& operator=(SimpleUniquePtr&& other) noexcept {
        if (this != &other) {
            delete ptr;
            ptr = other.ptr;
            other.ptr = nullptr;
        }
        return *this;
    }

    T& operator*() const { return *ptr; }
    T* operator->() const { return ptr; }
    T* get() const { return ptr; }

    explicit operator bool() const { return ptr != nullptr; }
};

// ============================================================
// 示範:RAII 如何防止例外導致的資源洩漏
// ============================================================

void riskyOperation(bool shouldThrow) {
    std::cout << "\n  進入 riskyOperation (shouldThrow="
              << (shouldThrow ? "true" : "false") << ")" << std::endl;

    MemoryBlock block("risk_block", 5);
    block[0] = 42;
    block[1] = 99;
    block.print();

    if (shouldThrow) {
        std::cout << "    💥 即將拋出例外!" << std::endl;
        throw std::runtime_error("模擬錯誤");
        // MemoryBlock 的解構子仍然會被呼叫 — RAII 保證!
    }

    std::cout << "    正常完成" << std::endl;
    // MemoryBlock 離開作用域,解構子自動釋放
}

// 對比:不使用 RAII 的危險寫法(僅說明,不實際執行)
void unsafeExample() {
    std::cout << "\n  ❌ 不使用 RAII 的危險寫法(僅為說明):" << std::endl;
    std::cout << R"(
    int* data = new int[100];
    // ... 如果這裡拋出例外 ...
    // data 永遠不會被 delete → 記憶體洩漏!
    delete[] data;
  )" << std::endl;

    std::cout << "  ✓ 使用 RAII 的安全寫法:" << std::endl;
    std::cout << R"(
    MemoryBlock block("safe", 100);
    // ... 即使這裡拋出例外 ...
    // block 的解構子仍會自動呼叫 → 不會洩漏!
  )" << std::endl;
}

// ============================================================
// main
// ============================================================

int main() {
    std::cout << "========================================" << std::endl;
    std::cout << " Ch09 RAII — 資源取得即初始化" << std::endl;
    std::cout << "========================================\n" << std::endl;

    // ------ 1. FileGuard ------
    std::cout << "【1】FileGuard — 檔案 RAII\n" << std::endl;

    {
        FileGuard file("/tmp/raii_demo.txt");
        file.writeLine("Hello RAII!");
        file.writeLine("這是自動管理的檔案。");
        file.writeLine("離開作用域時會自動關閉。");
        std::cout << "    即將離開區塊..." << std::endl;
    }
    std::cout << "    區塊已結束\n" << std::endl;

    // ------ 2. MemoryBlock ------
    std::cout << "【2】MemoryBlock — 記憶體 RAII\n" << std::endl;

    {
        MemoryBlock mb("data", 5);
        mb[0] = 10;
        mb[1] = 20;
        mb[2] = 30;
        mb.print();
        std::cout << "    即將離開區塊..." << std::endl;
    }
    std::cout << "    記憶體已自動釋放\n" << std::endl;

    // ------ 3. LockGuard ------
    std::cout << "【3】LockGuard — 鎖的 RAII\n" << std::endl;

    SimpleMutex mutex("資料鎖");
    {
        LockGuard guard(mutex);
        std::cout << "    (在臨界區中執行工作...)" << std::endl;
        std::cout << "    即將離開區塊..." << std::endl;
    }
    std::cout << "    鎖已自動釋放\n" << std::endl;

    // ------ 4. SimpleUniquePtr ------
    std::cout << "【4】SimpleUniquePtr — 智慧指標 RAII\n" << std::endl;

    {
        SimpleUniquePtr<int> p1(new int(42));
        std::cout << "    *p1 = " << *p1 << std::endl;

        SimpleUniquePtr<std::string> p2(new std::string("Hello RAII!"));
        std::cout << "    *p2 = " << *p2 << std::endl;
        std::cout << "    p2->length() = " << p2->length() << std::endl;

        // SimpleUniquePtr<int> p3 = p1;  // ✗ 編譯錯誤!禁止拷貝
        SimpleUniquePtr<int> p3(std::move(p1));  // ✓ 允許移動
        std::cout << "    移動後 p1 是否有效: " << (p1 ? "是" : "否") << std::endl;
        std::cout << "    *p3 = " << *p3 << std::endl;

        std::cout << "    即將離開區塊..." << std::endl;
    }
    std::cout << "    資源已自動釋放\n" << std::endl;

    // ------ 5. 例外安全示範 ------
    std::cout << "【5】RAII 在例外發生時的保護\n" << std::endl;

    // 正常情況
    try {
        riskyOperation(false);
    } catch (const std::exception& e) {
        std::cout << "    捕獲例外: " << e.what() << std::endl;
    }

    std::cout << std::endl;

    // 例外情況 — RAII 確保資源仍會被釋放
    try {
        riskyOperation(true);
    } catch (const std::exception& e) {
        std::cout << "    捕獲例外: " << e.what() << std::endl;
        std::cout << "    ☝ 注意:MemoryBlock 的解構子仍然被呼叫了!" << std::endl;
    }

    // ------ 6. 概念比較 ------
    std::cout << std::endl;
    std::cout << "【6】RAII vs 手動管理 — 概念比較" << std::endl;
    unsafeExample();

    // ------ 總結 ------
    std::cout << "【總結】RAII 的核心原則\n" << std::endl;
    std::cout << "  1. 建構子取得資源(開檔、配置記憶體、上鎖)" << std::endl;
    std::cout << "  2. 解構子釋放資源(關檔、釋放記憶體、解鎖)" << std::endl;
    std::cout << "  3. 利用 C++ 自動解構機制保證不洩漏" << std::endl;
    std::cout << "  4. 即使發生例外,解構子仍會被呼叫" << std::endl;
    std::cout << "  5. 標準函式庫大量使用此模式:" << std::endl;
    std::cout << "     - std::unique_ptr / std::shared_ptr" << std::endl;
    std::cout << "     - std::lock_guard / std::unique_lock" << std::endl;
    std::cout << "     - std::fstream" << std::endl;
    std::cout << "     - std::vector / std::string" << std::endl;

    std::cout << "\n========================================" << std::endl;
    std::cout << " 範例結束" << std::endl;
    std::cout << "========================================" << std::endl;

    return 0;
}

Artigos relacionados