S SmartDocs
系列: C++ cpp 602 行 · 更新于 2026-04-03

custom_exceptions.cpp

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

// ============================================================
// Ch18 — 自訂例外類別(Custom Exception Classes)
// 編譯:g++ -std=c++17 -Wall -o custom_exceptions custom_exceptions.cpp
// ============================================================

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

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

// ============================================================
// 1. 繼承 std::exception 的自訂例外
// ============================================================

class AppException : public std::exception {
public:
    explicit AppException(const std::string& message)
        : message_(message) {}

    const char* what() const noexcept override {
        return message_.c_str();
    }

private:
    std::string message_;
};

// ============================================================
// 2. 繼承 std::runtime_error 的自訂例外(更簡潔)
// ============================================================

class NetworkException : public std::runtime_error {
public:
    NetworkException(const std::string& message, int errorCode)
        : std::runtime_error(message), errorCode_(errorCode) {}

    int errorCode() const { return errorCode_; }

    std::string fullMessage() const {
        return std::string(what()) + " (錯誤碼: " + std::to_string(errorCode_) + ")";
    }

private:
    int errorCode_;
};

// ============================================================
// 3. 帶有上下文資訊的例外
// ============================================================

class DetailedException : public std::runtime_error {
public:
    DetailedException(const std::string& message,
                      const std::string& file,
                      int line,
                      const std::string& function)
        : std::runtime_error(message),
          file_(file), line_(line), function_(function) {}

    const std::string& file() const { return file_; }
    int line() const { return line_; }
    const std::string& function() const { return function_; }

    std::string detailedMessage() const {
        std::ostringstream oss;
        oss << what() << "\n"
            << "  位置: " << file_ << ":" << line_ << "\n"
            << "  函式: " << function_;
        return oss.str();
    }

private:
    std::string file_;
    int line_;
    std::string function_;
};

// 巨集簡化使用
#define THROW_DETAILED(msg) \
    throw DetailedException(msg, __FILE__, __LINE__, __func__)

// ============================================================
// 4. 計算器例外階層
// ============================================================

class MathException : public std::runtime_error {
public:
    MathException(const std::string& msg, const std::string& operation)
        : std::runtime_error(msg), operation_(operation) {}
    const std::string& operation() const { return operation_; }
private:
    std::string operation_;
};

class DivisionByZeroException : public MathException {
public:
    DivisionByZeroException(double numerator)
        : MathException("除以零", "division"),
          numerator_(numerator) {}
    double numerator() const { return numerator_; }
private:
    double numerator_;
};

class NegativeSqrtException : public MathException {
public:
    explicit NegativeSqrtException(double value)
        : MathException("負數不能取平方根", "sqrt"),
          value_(value) {}
    double value() const { return value_; }
private:
    double value_;
};

class OverflowException : public MathException {
public:
    OverflowException(double a, double b)
        : MathException("計算結果溢位", "multiply"),
          a_(a), b_(b) {}
    double a() const { return a_; }
    double b() const { return b_; }
private:
    double a_, b_;
};

// 計算器類別
class Calculator {
public:
    static double divide(double a, double b) {
        if (b == 0.0) {
            throw DivisionByZeroException(a);
        }
        return a / b;
    }

    static double squareRoot(double x) {
        if (x < 0) {
            throw NegativeSqrtException(x);
        }
        return std::sqrt(x);
    }

    static double multiply(double a, double b) {
        double result = a * b;
        if (std::isinf(result)) {
            throw OverflowException(a, b);
        }
        return result;
    }

    static double power(double base, int exp) {
        if (base == 0 && exp < 0) {
            throw DivisionByZeroException(0);
        }
        double result = std::pow(base, exp);
        if (std::isinf(result)) {
            throw OverflowException(base, exp);
        }
        return result;
    }
};

// ============================================================
// 5. 銀行帳戶例外
// ============================================================

class BankException : public std::runtime_error {
public:
    BankException(const std::string& msg, const std::string& accountId)
        : std::runtime_error(msg), accountId_(accountId) {}
    const std::string& accountId() const { return accountId_; }
private:
    std::string accountId_;
};

class InsufficientFundsException : public BankException {
public:
    InsufficientFundsException(const std::string& accountId,
                               double balance,
                               double requestedAmount)
        : BankException("餘額不足", accountId),
          balance_(balance), requestedAmount_(requestedAmount) {}

    double balance() const { return balance_; }
    double requestedAmount() const { return requestedAmount_; }
    double shortfall() const { return requestedAmount_ - balance_; }

    std::string detail() const {
        std::ostringstream oss;
        oss << what() << " — 帳戶 " << accountId()
            << ":餘額 $" << balance_
            << ",請求提取 $" << requestedAmount_
            << ",差額 $" << shortfall();
        return oss.str();
    }

private:
    double balance_;
    double requestedAmount_;
};

class AccountNotFoundException : public BankException {
public:
    explicit AccountNotFoundException(const std::string& accountId)
        : BankException("找不到帳戶", accountId) {}
};

class InvalidAmountException : public BankException {
public:
    InvalidAmountException(const std::string& accountId, double amount)
        : BankException("金額無效", accountId), amount_(amount) {}
    double amount() const { return amount_; }
private:
    double amount_;
};

// 銀行帳戶類別
class BankAccount {
public:
    BankAccount(const std::string& id, const std::string& owner, double balance)
        : id_(id), owner_(owner), balance_(balance) {}

    void deposit(double amount) {
        if (amount <= 0) {
            throw InvalidAmountException(id_, amount);
        }
        balance_ += amount;
        std::cout << "  存入 $" << amount << ",餘額 $" << balance_ << "\n";
    }

    void withdraw(double amount) {
        if (amount <= 0) {
            throw InvalidAmountException(id_, amount);
        }
        if (amount > balance_) {
            throw InsufficientFundsException(id_, balance_, amount);
        }
        balance_ -= amount;
        std::cout << "  提取 $" << amount << ",餘額 $" << balance_ << "\n";
    }

    void transfer(BankAccount& to, double amount) {
        std::cout << "  轉帳 $" << amount << " 從 " << id_ << " 到 " << to.id_ << "\n";
        withdraw(amount);    // 可能拋出 InsufficientFundsException
        try {
            to.deposit(amount);
        }
        catch (...) {
            balance_ += amount;  // 回復(強烈保證)
            throw;
        }
    }

    std::string info() const {
        std::ostringstream oss;
        oss << "帳戶 " << id_ << " (" << owner_ << "): $" << balance_;
        return oss.str();
    }

    double balance() const { return balance_; }

private:
    std::string id_;
    std::string owner_;
    double balance_;
};

// ============================================================
// 6. noexcept 示範
// ============================================================

void noexceptFunc() noexcept {
    // 保證不拋出例外
    // 如果違反,程式呼叫 std::terminate
}

void mayThrowFunc() {
    throw std::runtime_error("我會拋出例外");
}

class NoexceptDemo {
public:
    NoexceptDemo() = default;
    ~NoexceptDemo() = default;  // 解構子預設 noexcept

    // 移動操作標記 noexcept — 讓 vector 能使用移動
    NoexceptDemo(NoexceptDemo&&) noexcept = default;
    NoexceptDemo& operator=(NoexceptDemo&&) noexcept = default;

    NoexceptDemo(const NoexceptDemo&) = default;
    NoexceptDemo& operator=(const NoexceptDemo&) = default;
};

int main() {
    std::cout << "===== Ch18:自訂例外類別示範 =====\n";

    // ──────────────────────────────────────────
    // 1. 基本自訂例外
    // ──────────────────────────────────────────
    section("1. 繼承 std::exception");
    {
        try {
            throw AppException("這是自訂的 AppException");
        }
        catch (const std::exception& e) {
            std::cout << "捕捉到: " << e.what() << "\n";
        }
    }

    // ──────────────────────────────────────────
    // 2. 帶有額外資料的例外
    // ──────────────────────────────────────────
    section("2. 帶有額外資料的例外");
    {
        try {
            throw NetworkException("連線逾時", 408);
        }
        catch (const NetworkException& e) {
            std::cout << "捕捉到 NetworkException:\n";
            std::cout << "  訊息: " << e.what() << "\n";
            std::cout << "  錯誤碼: " << e.errorCode() << "\n";
            std::cout << "  完整訊息: " << e.fullMessage() << "\n";
        }
    }

    // ──────────────────────────────────────────
    // 3. 帶有位置資訊的例外
    // ──────────────────────────────────────────
    section("3. 帶有位置資訊的例外");
    {
        try {
            THROW_DETAILED("偵測到無效狀態");
        }
        catch (const DetailedException& e) {
            std::cout << "捕捉到 DetailedException:\n";
            std::cout << e.detailedMessage() << "\n";
        }
    }

    // ──────────────────────────────────────────
    // 4. 計算器例外階層
    // ──────────────────────────────────────────
    section("4. 計算器例外階層");
    {
        struct TestCase {
            std::string description;
            std::function<double()> operation;
        };

        std::vector<TestCase> tests = {
            {"10 / 3",    []() { return Calculator::divide(10, 3); }},
            {"10 / 0",    []() { return Calculator::divide(10, 0); }},
            {"sqrt(25)",  []() { return Calculator::squareRoot(25); }},
            {"sqrt(-4)",  []() { return Calculator::squareRoot(-4); }},
            {"1e308 * 2", []() { return Calculator::multiply(1e308, 2); }},
            {"0 ^ -1",   []() { return Calculator::power(0, -1); }},
        };

        for (const auto& test : tests) {
            try {
                double result = test.operation();
                std::cout << test.description << " = " << result << "\n";
            }
            catch (const DivisionByZeroException& e) {
                std::cout << test.description << " → [DivisionByZero] "
                          << e.what() << " (分子=" << e.numerator() << ")\n";
            }
            catch (const NegativeSqrtException& e) {
                std::cout << test.description << " → [NegativeSqrt] "
                          << e.what() << " (值=" << e.value() << ")\n";
            }
            catch (const OverflowException& e) {
                std::cout << test.description << " → [Overflow] "
                          << e.what() << "\n";
            }
            catch (const MathException& e) {
                std::cout << test.description << " → [MathException] "
                          << e.what() << " (操作: " << e.operation() << ")\n";
            }
        }
    }

    // ──────────────────────────────────────────
    // 5. 銀行帳戶系統
    // ──────────────────────────────────────────
    section("5. 銀行帳戶系統");
    {
        BankAccount alice("A001", "Alice", 1000.0);
        BankAccount bob("B002", "Bob", 500.0);

        std::cout << "初始狀態:\n";
        std::cout << "  " << alice.info() << "\n";
        std::cout << "  " << bob.info() << "\n\n";

        // 正常存款
        std::cout << "--- Alice 存入 $200 ---\n";
        try {
            alice.deposit(200);
        }
        catch (const BankException& e) {
            std::cout << "  錯誤: " << e.what() << "\n";
        }

        // 正常提款
        std::cout << "\n--- Alice 提取 $300 ---\n";
        try {
            alice.withdraw(300);
        }
        catch (const BankException& e) {
            std::cout << "  錯誤: " << e.what() << "\n";
        }

        // 餘額不足
        std::cout << "\n--- Bob 嘗試提取 $1000 ---\n";
        try {
            bob.withdraw(1000);
        }
        catch (const InsufficientFundsException& e) {
            std::cout << "  " << e.detail() << "\n";
        }

        // 無效金額
        std::cout << "\n--- Alice 嘗試存入 $-50 ---\n";
        try {
            alice.deposit(-50);
        }
        catch (const InvalidAmountException& e) {
            std::cout << "  錯誤: " << e.what()
                      << "(帳戶 " << e.accountId()
                      << ",金額 $" << e.amount() << ")\n";
        }

        // 正常轉帳
        std::cout << "\n--- Alice 轉帳 $200 給 Bob ---\n";
        try {
            alice.transfer(bob, 200);
        }
        catch (const BankException& e) {
            std::cout << "  轉帳失敗: " << e.what() << "\n";
        }

        // 轉帳餘額不足
        std::cout << "\n--- Bob 嘗試轉帳 $2000 給 Alice ---\n";
        try {
            bob.transfer(alice, 2000);
        }
        catch (const InsufficientFundsException& e) {
            std::cout << "  " << e.detail() << "\n";
        }

        std::cout << "\n最終狀態:\n";
        std::cout << "  " << alice.info() << "\n";
        std::cout << "  " << bob.info() << "\n";
    }

    // ──────────────────────────────────────────
    // 6. 例外階層的多型捕捉
    // ──────────────────────────────────────────
    section("6. 例外階層的多型捕捉");
    {
        std::cout << "銀行例外階層:\n";
        std::cout << R"(
  BankException (繼承 std::runtime_error)
  ├── InsufficientFundsException
  ├── AccountNotFoundException
  └── InvalidAmountException
)";

        std::cout << "\n不同層級的捕捉:\n";

        auto throwTest = []() {
            throw InsufficientFundsException("X001", 100, 500);
        };

        // 捕捉具體型別
        try { throwTest(); }
        catch (const InsufficientFundsException& e) {
            std::cout << "  InsufficientFundsException: "
                      << e.what() << " (差額 $" << e.shortfall() << ")\n";
        }

        // 捕捉中間基底類別
        try { throwTest(); }
        catch (const BankException& e) {
            std::cout << "  BankException: " << e.what()
                      << " (帳戶: " << e.accountId() << ")\n";
        }

        // 捕捉 std::exception
        try { throwTest(); }
        catch (const std::exception& e) {
            std::cout << "  std::exception: " << e.what() << "\n";
        }
    }

    // ──────────────────────────────────────────
    // 7. noexcept 規範
    // ──────────────────────────────────────────
    section("7. noexcept 規範");
    {
        std::cout << "noexcept 檢查:\n";
        std::cout << "  noexceptFunc: "
                  << (noexcept(noexceptFunc()) ? "noexcept" : "可拋出") << "\n";
        std::cout << "  mayThrowFunc: "
                  << (noexcept(mayThrowFunc()) ? "noexcept" : "可拋出") << "\n";

        // 移動操作的 noexcept
        std::cout << "\nNoexceptDemo 的移動建構子: "
                  << (noexcept(NoexceptDemo(std::declval<NoexceptDemo&&>()))
                      ? "noexcept" : "可拋出") << "\n";

        std::cout << "\nnoexcept 重要規則:\n";
        std::cout << "  1. 解構子預設是 noexcept\n";
        std::cout << "  2. 移動操作應標記 noexcept\n";
        std::cout << "  3. swap 函式應標記 noexcept\n";
        std::cout << "  4. noexcept 函式若拋出 → std::terminate\n";

        // 條件式 noexcept
        std::cout << "\n條件式 noexcept 示範:\n";
        std::cout << R"(
  template<typename T>
  void swap(T& a, T& b) noexcept(noexcept(T(std::move(a)))) {
      T temp = std::move(a);
      a = std::move(b);
      b = std::move(temp);
  }
  // → 如果 T 的移動建構是 noexcept,swap 也是 noexcept
)";
    }

    // ──────────────────────────────────────────
    // 8. 例外安全保證
    // ──────────────────────────────────────────
    section("8. 例外安全保證");
    {
        std::cout << "三個層級的例外安全保證:\n\n";

        std::cout << "【不拋出保證(No-throw Guarantee)】\n";
        std::cout << "  操作保證不會拋出例外\n";
        std::cout << "  適用:解構子、移動操作、swap\n";
        std::cout << "  標記:noexcept\n\n";

        std::cout << "【強烈保證(Strong Guarantee)】\n";
        std::cout << "  操作若失敗,狀態回復到呼叫前(如同資料庫的交易)\n";
        std::cout << "  實作方式:Copy-and-Swap 慣用法\n";
        std::cout << R"(
  MyClass& operator=(const MyClass& other) {
      MyClass temp(other);   // 複製(可能拋出)
      swap(*this, temp);     // 交換(noexcept)
      return *this;
  }  // temp 釋放舊資源(noexcept)
)";

        std::cout << "\n【基本保證(Basic Guarantee)】\n";
        std::cout << "  操作若失敗,物件仍處於有效狀態\n";
        std::cout << "  但內容可能已改變(不保證回復原狀)\n";
        std::cout << "  最低要求:不洩漏資源、不留下無效狀態\n";

        // 強烈保證的示範:BankAccount::transfer
        std::cout << "\n--- BankAccount::transfer 具有強烈保證 ---\n";
        BankAccount a("T1", "Test-A", 100);
        BankAccount b("T2", "Test-B", 0);
        std::cout << "轉帳前:A=$" << a.balance() << ", B=$" << b.balance() << "\n";
        try {
            a.transfer(b, 200);  // 餘額不足
        }
        catch (const InsufficientFundsException&) {
            std::cout << "轉帳失敗(餘額不足)\n";
        }
        std::cout << "轉帳後:A=$" << a.balance() << ", B=$" << b.balance() << "\n";
        std::cout << "→ 兩個帳戶的餘額都沒有改變(強烈保證 ✓)\n";
    }

    // ──────────────────────────────────────────
    // 9. 例外設計最佳實踐
    // ──────────────────────────────────────────
    section("9. 例外設計最佳實踐");
    {
        std::cout << "1. 總是以 const& 捕捉例外\n";
        std::cout << "2. 繼承自 std::exception 或其子類別\n";
        std::cout << "3. 覆寫 what() 回傳有意義的訊息\n";
        std::cout << "4. 攜帶足夠的上下文資訊(錯誤碼、檔案名、行號等)\n";
        std::cout << "5. 設計合理的例外階層(不要太深也不要太平)\n";
        std::cout << "6. 解構子永遠不要拋出例外\n";
        std::cout << "7. 移動操作標記 noexcept\n";
        std::cout << "8. 用 throw; 重新拋出(保留型別資訊)\n";
        std::cout << "9. 例外用於「異常」情況,不用於正常控制流程\n";
        std::cout << "10. 考慮例外安全性(至少提供基本保證)\n";
    }

    std::cout << "\n===== 自訂例外類別示範結束 =====\n";
    return 0;
}

相关文章