S SmartDocs
Série: C++ cpp 422 lignes · Mis à jour 2026-04-03

exceptions.cpp

C++/Part4_現代CPP/Ch18_例外處理/exceptions.cpp

// ============================================================
// Ch18 — 例外處理基礎(Exception Handling Basics)
// 編譯:g++ -std=c++17 -Wall -o exceptions exceptions.cpp
// ============================================================

#include <iostream>
#include <string>
#include <vector>
#include <stdexcept>
#include <fstream>
#include <sstream>

// ── 輔助函式 ──
void section(const std::string& title) {
    std::cout << "\n========================================\n";
    std::cout << "  " << title << "\n";
    std::cout << "========================================\n";
}

// ── 堆疊回溯示範用類別 ──
class TrackedResource {
public:
    explicit TrackedResource(const std::string& name) : name_(name) {
        std::cout << "  TrackedResource(\"" << name_ << "\") 建構\n";
    }
    ~TrackedResource() {
        std::cout << "  TrackedResource(\"" << name_ << "\") 解構(堆疊回溯)\n";
    }
private:
    std::string name_;
};

// ── 層層呼叫的函式(展示堆疊回溯)──
void level3() {
    TrackedResource r3("Level-3 資源");
    std::cout << "  level3:即將拋出例外...\n";
    throw std::runtime_error("在 level3 發生錯誤");
}

void level2() {
    TrackedResource r2("Level-2 資源");
    std::cout << "  level2:呼叫 level3...\n";
    level3();
    std::cout << "  level2:這行不會被執行\n";
}

void level1() {
    TrackedResource r1("Level-1 資源");
    std::cout << "  level1:呼叫 level2...\n";
    level2();
    std::cout << "  level1:這行不會被執行\n";
}

// ── 安全除法 ──
double safeDivide(double numerator, double denominator) {
    if (denominator == 0.0) {
        throw std::invalid_argument("除數不能為零");
    }
    return numerator / denominator;
}

// ── 陣列存取 ──
int safeAccess(const std::vector<int>& vec, int index) {
    if (index < 0 || static_cast<size_t>(index) >= vec.size()) {
        throw std::out_of_range(
            "索引 " + std::to_string(index) +
            " 超出範圍 [0, " + std::to_string(vec.size() - 1) + "]"
        );
    }
    return vec[static_cast<size_t>(index)];
}

// ── 解析整數 ──
int parseInteger(const std::string& str) {
    try {
        size_t pos = 0;
        int result = std::stoi(str, &pos);
        if (pos != str.length()) {
            throw std::invalid_argument("字串包含非數字字元:\"" + str + "\"");
        }
        return result;
    }
    catch (const std::out_of_range&) {
        throw std::out_of_range("數值超出 int 範圍:\"" + str + "\"");
    }
    catch (const std::invalid_argument&) {
        throw std::invalid_argument("無法解析為整數:\"" + str + "\"");
    }
}

// ── 模擬檔案操作 ──
std::string readFile(const std::string& filename) {
    std::ifstream file(filename);
    if (!file.is_open()) {
        throw std::runtime_error("無法開啟檔案:" + filename);
    }

    std::stringstream buffer;
    buffer << file.rdbuf();
    return buffer.str();
}

int main() {
    std::cout << "===== Ch18:例外處理基礎示範 =====\n";

    // ──────────────────────────────────────────
    // 1. 基本 try / catch / throw
    // ──────────────────────────────────────────
    section("1. 基本 try / catch / throw");
    {
        // 拋出與捕捉 int
        try {
            std::cout << "嘗試拋出 int...\n";
            throw 42;
        }
        catch (int e) {
            std::cout << "捕捉到 int: " << e << "\n";
        }

        // 拋出與捕捉 const char*
        try {
            std::cout << "\n嘗試拋出 const char*...\n";
            throw "Something went wrong";
        }
        catch (const char* msg) {
            std::cout << "捕捉到字串: " << msg << "\n";
        }

        // 拋出與捕捉 std::string
        try {
            std::cout << "\n嘗試拋出 std::string...\n";
            throw std::string("String exception");
        }
        catch (const std::string& msg) {
            std::cout << "捕捉到 std::string: " << msg << "\n";
        }
    }

    // ──────────────────────────────────────────
    // 2. 標準例外
    // ──────────────────────────────────────────
    section("2. 標準例外");
    {
        // runtime_error
        try {
            throw std::runtime_error("執行時錯誤示範");
        }
        catch (const std::runtime_error& e) {
            std::cout << "runtime_error: " << e.what() << "\n";
        }

        // invalid_argument
        try {
            throw std::invalid_argument("引數無效");
        }
        catch (const std::invalid_argument& e) {
            std::cout << "invalid_argument: " << e.what() << "\n";
        }

        // out_of_range(來自 std::vector::at)
        try {
            std::vector<int> v = {1, 2, 3};
            std::cout << "\n存取 v.at(10)...\n";
            std::cout << v.at(10) << "\n";  // 會拋出 out_of_range
        }
        catch (const std::out_of_range& e) {
            std::cout << "out_of_range: " << e.what() << "\n";
        }

        // overflow_error
        try {
            throw std::overflow_error("數值溢位");
        }
        catch (const std::overflow_error& e) {
            std::cout << "overflow_error: " << e.what() << "\n";
        }
    }

    // ──────────────────────────────────────────
    // 3. 以參考捕捉(Catch by Reference)
    // ──────────────────────────────────────────
    section("3. 以參考捕捉的重要性");
    {
        // 正確:以 const 參考捕捉
        try {
            throw std::runtime_error("衍生類別的錯誤");
        }
        catch (const std::exception& e) {
            // 即使捕捉基底類別,what() 仍回傳正確訊息
            std::cout << "以 const& 捕捉: " << e.what() << "\n";
        }

        std::cout << "\n以值捕捉會導致物件切割(slicing):\n";
        std::cout << R"(
  // 不好的做法:
  catch (std::exception e) {
      // 如果拋出的是 runtime_error,
      // 會被切割成 exception,遺失額外資訊
  }
)";
        std::cout << "→ 永遠使用 const& 捕捉例外\n";
    }

    // ──────────────────────────────────────────
    // 4. 多個 catch 區塊
    // ──────────────────────────────────────────
    section("4. 多個 catch 區塊(順序很重要)");
    {
        auto testException = [](int type) {
            try {
                switch (type) {
                    case 1: throw std::out_of_range("越界");
                    case 2: throw std::invalid_argument("無效引數");
                    case 3: throw std::runtime_error("執行時錯誤");
                    case 4: throw 999;
                    default: break;
                }
            }
            // 衍生類別在前
            catch (const std::out_of_range& e) {
                std::cout << "  [out_of_range] " << e.what() << "\n";
            }
            catch (const std::invalid_argument& e) {
                std::cout << "  [invalid_argument] " << e.what() << "\n";
            }
            // 基底類別在後
            catch (const std::exception& e) {
                std::cout << "  [exception] " << e.what() << "\n";
            }
            // 萬用捕捉放最後
            catch (...) {
                std::cout << "  [catch-all] 未知例外\n";
            }
        };

        std::cout << "type=1: "; testException(1);
        std::cout << "type=2: "; testException(2);
        std::cout << "type=3: "; testException(3);
        std::cout << "type=4: "; testException(4);
    }

    // ──────────────────────────────────────────
    // 5. catch-all(...)
    // ──────────────────────────────────────────
    section("5. catch-all (...)");
    {
        try {
            throw 3.14;  // 拋出 double
        }
        catch (const std::exception& e) {
            std::cout << "標準例外: " << e.what() << "\n";
        }
        catch (...) {
            std::cout << "catch(...) 捕捉到未知型別的例外\n";
            std::cout << "(無法取得例外資訊)\n";
        }
    }

    // ──────────────────────────────────────────
    // 6. 堆疊回溯(Stack Unwinding)
    // ──────────────────────────────────────────
    section("6. 堆疊回溯");
    {
        std::cout << "呼叫 level1 → level2 → level3,在 level3 拋出例外:\n\n";
        try {
            level1();
        }
        catch (const std::runtime_error& e) {
            std::cout << "\n捕捉到例外:" << e.what() << "\n";
            std::cout << "所有資源已透過解構子自動釋放 ✓\n";
        }
    }

    // ──────────────────────────────────────────
    // 7. 巢狀 try/catch 與重新拋出
    // ──────────────────────────────────────────
    section("7. 巢狀 try/catch 與重新拋出");
    {
        try {
            try {
                throw std::runtime_error("原始錯誤");
            }
            catch (const std::runtime_error& e) {
                std::cout << "內層捕捉:" << e.what() << "\n";
                std::cout << "記錄錯誤後重新拋出...\n";
                throw;  // 重新拋出(保留原始型別)
            }
        }
        catch (const std::exception& e) {
            std::cout << "外層捕捉:" << e.what() << "\n";
        }

        std::cout << "\n注意 throw vs throw e 的差異:\n";
        std::cout << "  throw;    → 重新拋出原始例外(保留型別)\n";
        std::cout << "  throw e;  → 拋出 e 的副本(可能切割)\n";
    }

    // ──────────────────────────────────────────
    // 8. 實際應用:安全除法
    // ──────────────────────────────────────────
    section("8. 實際應用:安全除法");
    {
        double pairs[][2] = {{10, 3}, {100, 0}, {-7, 2}, {0, 5}};

        for (auto& pair : pairs) {
            try {
                double result = safeDivide(pair[0], pair[1]);
                std::cout << pair[0] << " / " << pair[1]
                          << " = " << result << "\n";
            }
            catch (const std::invalid_argument& e) {
                std::cout << pair[0] << " / " << pair[1]
                          << " → 錯誤:" << e.what() << "\n";
            }
        }
    }

    // ──────────────────────────────────────────
    // 9. 實際應用:安全陣列存取
    // ──────────────────────────────────────────
    section("9. 實際應用:安全陣列存取");
    {
        std::vector<int> data = {10, 20, 30, 40, 50};
        int indices[] = {0, 2, 4, 5, -1};

        for (int idx : indices) {
            try {
                int val = safeAccess(data, idx);
                std::cout << "data[" << idx << "] = " << val << "\n";
            }
            catch (const std::out_of_range& e) {
                std::cout << "data[" << idx << "] → " << e.what() << "\n";
            }
        }
    }

    // ──────────────────────────────────────────
    // 10. 實際應用:字串轉整數
    // ──────────────────────────────────────────
    section("10. 實際應用:字串解析");
    {
        std::string testCases[] = {
            "42", "-7", "3.14", "abc", "999999999999999999", "123abc"
        };

        for (const auto& s : testCases) {
            try {
                int val = parseInteger(s);
                std::cout << "\"" << s << "\" → " << val << "\n";
            }
            catch (const std::invalid_argument& e) {
                std::cout << "\"" << s << "\" → [invalid_argument] "
                          << e.what() << "\n";
            }
            catch (const std::out_of_range& e) {
                std::cout << "\"" << s << "\" → [out_of_range] "
                          << e.what() << "\n";
            }
        }
    }

    // ──────────────────────────────────────────
    // 11. 實際應用:檔案操作
    // ──────────────────────────────────────────
    section("11. 實際應用:檔案操作");
    {
        try {
            std::string content = readFile("不存在的檔案.txt");
            std::cout << "檔案內容:" << content << "\n";
        }
        catch (const std::runtime_error& e) {
            std::cout << "錯誤:" << e.what() << "\n";
        }
    }

    // ──────────────────────────────────────────
    // 12. 例外與 RAII
    // ──────────────────────────────────────────
    section("12. 例外與 RAII 的完美搭配");
    {
        std::cout << "RAII 確保即使在例外路徑上,資源也能正確釋放:\n\n";

        try {
            std::string s = "會被自動釋放的字串";
            std::vector<int> v = {1, 2, 3, 4, 5};
            // std::unique_ptr<Resource> r = std::make_unique<Resource>(...);
            // std::lock_guard<std::mutex> lock(mtx);
            // std::fstream file("data.txt");

            std::cout << "  s = \"" << s << "\"\n";
            std::cout << "  v.size() = " << v.size() << "\n";

            throw std::runtime_error("模擬錯誤");

            // 以下不會執行,但 s, v 等物件的解構子會被呼叫
        }
        catch (const std::exception& e) {
            std::cout << "\n捕捉到例外:" << e.what() << "\n";
            std::cout << "所有 RAII 物件(string, vector 等)已自動清理 ✓\n";
        }
    }

    // ──────────────────────────────────────────
    // 13. 例外 vs 錯誤碼
    // ──────────────────────────────────────────
    section("13. 例外 vs 錯誤碼比較");
    {
        std::cout << "┌──────────────┬─────────────┬─────────────┐\n";
        std::cout << "│ 比較項目      │ 例外         │ 錯誤碼       │\n";
        std::cout << "├──────────────┼─────────────┼─────────────┤\n";
        std::cout << "│ 可忽略性      │ 不可忽略     │ 容易被忽略   │\n";
        std::cout << "│ 程式碼清晰度  │ 高           │ 低(多 if) │\n";
        std::cout << "│ 正常路徑效能  │ 零/極低開銷  │ 每次都檢查   │\n";
        std::cout << "│ 錯誤路徑效能  │ 較慢         │ 快           │\n";
        std::cout << "│ 建構子錯誤    │ 唯一選擇     │ 無法使用     │\n";
        std::cout << "│ 適用場景      │ 罕見錯誤     │ 可預期結果   │\n";
        std::cout << "└──────────────┴─────────────┴─────────────┘\n";
    }

    std::cout << "\n===== 例外處理基礎示範結束 =====\n";
    return 0;
}

Articles liés