---
# System prepended metadata

title: 【C++ 筆記】例外處理（Exception Handling） - part 28
tags: [程式設計, C++, 程式語言]

---

【C++ 筆記】例外處理（Exception Handling） - part 28
===

目錄（Table of Contents）：

[TOC]

---

很感謝你點進來這篇文章。

你好，我並不是什麼 C++、程式語言的專家，所以本文若有些錯誤麻煩請各位鞭大力一點，我極需各位的指正及指導！！本系列文章的性質主要以詼諧的口吻，一派輕鬆的態度自學程式語言，如果你喜歡，麻煩留言說聲文章讚讚吧！

例外是什麼？
---

例外（exception）又稱為異常，是指程式在執行過程當中發生的例外情況或錯誤事件。如將兩數相除的過程中，有一個數除於 0，這會是一個例外，因為可能會導致未定義的錯誤。

而處理例外的過程就稱為例外處理，對 XD。

基本例外處理
---

### try-catch

語法：

```cpp=
try {
    // 這可能會拋出一個例外
    // Code that might throw an exception
} 
catch (ExceptionType e) {   
    // 例外處理的地方
    // exception handling code
}
```

當 try 區塊發生例外的時候，於 try 的區塊會停止，catch 就會去捕捉這個例外，然後在 catch 區塊裡面做一些處理。

ExceptionType e：看 catch 要捕捉什麼，如 `int e` 就是捕捉 int 型態的例外。

### throw 拋出例外

用 throw 關鍵字拋出例外，catch 會偵測這個例外是什麼東西，如果符合的話就會去處理，如：

```cpp=
try {         
    throw val
} 
catch (ExceptionType e) {   
    // exception handling code
}
```

範例：

```cpp=
#include <iostream>
#include <string>

using namespace std;

int main(){
    // 整數型態的例外捕捉
    try {
        int val = 10;
        if (val > 5){
            throw val;
        }
        cout << "No Exception." << endl;
    }
    catch (int e){
        cout << "Caught an integer exception with value : " << e << endl;
    }
    
    // 字串型態的例外捕捉
    try{
        string error_msg = "Something wrong!";
        throw error_msg;
    }
    catch (string e){
        cout << "Caught a string exception : " << e << endl;
    }
    return 0;
}
```

Output：

```
Caught an integer exception with value : 10
Caught a string exception : Something wrong!
```

標準例外
---

標準例外是 C++ STL 裡面定義的一組例外類別，大多被定義在 `<stdexcept>` 或 `<typeinfo>`（如 `bad_cast`） 中。

主要分成兩大類：

- 邏輯錯誤（logic_error）
- 執行期錯誤（runtime_error）

以下是標準錯誤的層次結構圖：

![cpp-exception-hierarchy](https://hackmd.io/_uploads/B1zh35yBle.png)

Image Source：[GeeksForGeeks](https://www.geeksforgeeks.org/exception-handling-c/)

以下是為以上層次結構圖製作的表格：

| 例外類型     | 說明                                              |
| ------------------------ | ------------------------------------------------------------ |
| `std::logic_error`       | 邏輯錯誤。表示程式中存在不合理的邏輯問題，通常是可預先檢查出的錯誤。                           |
| `std::invalid_argument`  | 無效的參數。通常因傳遞了錯誤或非法的參數值而引發。屬於 `logic_error` 的子類。               |
| `std::domain_error`      | 數學定義域錯誤。當參數不在函數允許的數學定義域內時拋出，例如平方根的負數輸入。屬於 `logic_error` 的子類。 |
| `std::length_error`      | 長度錯誤。當容器長度超過其最大容量限制時拋出。屬於 `logic_error` 的子類。                 |
| `std::out_of_range`      | 超出範圍。當使用無效索引存取容器元素時拋出。屬於 `logic_error` 的子類。                  |
| `std::runtime_error`     | 執行期間錯誤。表示在執行階段發生非預期狀況，無法在編譯期間預測。                             |
| `std::range_error`       | 範圍錯誤。當數值運算結果超出有效範圍但仍合法（如浮點精度損失）時拋出。屬於 `runtime_error` 的子類。   |
| `std::overflow_error`    | 溢位錯誤。當數值計算超過資料型態的上限時拋出。屬於 `runtime_error` 的子類。               |
| `std::underflow_error`   | 下溢錯誤。當浮點數計算結果太接近零而無法正確表示時拋出。屬於 `runtime_error` 的子類。          |
| `std::bad_alloc`         | 記憶體配置失敗。當 `new` 無法配置足夠記憶體時拋出。屬於 `std::exception` 的子類。        |
| `std::bad_function_call` | 錯誤的函數呼叫。當呼叫尚未設定目標的 `std::function` 物件時拋出。                    |
| `std::bad_cast`          | 錯誤的型態轉換。當使用 `dynamic_cast` 進行不合法的轉型時拋出。                      |

在以上每個標準例外中，都有一個 what() 方法提供這些標準例外的相關資訊。

以下是個範例：

```cpp=
#include <iostream>
#include <stdexcept>
#include <cmath>

using namespace std;

double safe_sqrt(double x){
    if (x < 0){
        throw domain_error("輸入錯誤！平方根的定義域為正數");
    }
    return sqrt(x);
}

int main(){
    try{
        double val = -5.0;
        cout << "計算 " << val << " 的平方根..." << endl;
        double result = safe_sqrt(val);
        cout << "結果是: " << result << endl;
    }
    catch (domain_error e){
        cout << "捕捉到 domain_error 例外: " << e.what() << endl;
    }
    return 0;
}
```

Output：

```
計算 -5 的平方根...
捕捉到 domain_error 例外: 輸入錯誤！平方根的定義域為正數
```

catch 多個例外
---

不只是 catch 一個例外而已，也可以多個例外：

```cpp=
try {         
    // Code that might throw an exception
} 
catch (type1 e) {   
    // executed when exception is of type1
}
catch (type2 e) {   
    // executed when exception is of type2
}
catch (...) {
    // executed when no matching catch is found
}
```

以下是個範例：

```cpp=
#include <iostream>
#include <stdexcept>

using namespace std;

int divide(int a, int b){
    if (b == 0){
        throw invalid_argument("除數不能為0");
    }
    if (a == 0){
        throw 0;
    }
    return a / b;
}

int main(){
    int x, y;
    cout << "請輸入兩個整數 (a, b) :";
    cin >> x >> y;
    
    try{
        int result = divide(x, y);
        cout << "除法運算結果 : " << result << endl;
    }
    catch (invalid_argument e){
        cout << "捕捉到 invalid_argument 例外: " << e.what() << endl;
    }
    catch (int e){
        cout << "捕捉到整數型態例外，值為: " << e << endl;
    }
    
    return 0;
}
```

### catch 所有例外

就是 `catch(...)` 即可捕捉所有的例外，延續上個範例，增加這個上去：

```cpp=
#include <iostream>
#include <stdexcept>

using namespace std;

int divide(int a, int b){
    if (a == 9999){ // 新增處
        throw "abc";
    }
    if (b == 0){
        throw invalid_argument("除數不能為0");
    }
    if (a == 0){
        throw 0;
    }
    return a / b;
}

int main(){
    int x, y;
    cout << "請輸入兩個整數 (a, b) :";
    cin >> x >> y;
    
    try{
        int result = divide(x, y);
        cout << "除法運算結果 : " << result << endl;
    }
    catch (invalid_argument e){
        cout << "捕捉到 invalid_argument 例外: " << e.what() << endl;
    }
    catch (int e){
        cout << "捕捉到整數型態例外，值為: " << e << endl;
    }
    catch (...){ // 新增處
        cout << "捕捉到未知的例外";
    }
    
    return 0;
}
```

當輸入 a = 9999, b = 任意數時，就會輸出「捕捉到未知的例外」。

### catch by reference

這個方法只需要傳遞對拋出例外的參考，不需要建立他的 copy，能減少這部分的效能開銷。除了這之外，主要拿來捕捉多型的例外。

```cpp=
#include <iostream>
#include <stdexcept>

using namespace std;

int main() {
    try {
        throw runtime_error("發生錯誤：無效的操作");
    }
    catch (const runtime_error& e) {
        cout << "捕捉到例外: " << e.what() << endl;
    }

    return 0;
}
```

Output：

```
捕捉到例外: 發生錯誤：無效的操作
```

例外規格（Exception Specification）
---

1. `noexcept` or `noexcept(true)`：如其名，告訴函數保證不會引發例外。
2. `noexcept(false)`：表示可能會拋出例外。若未使用任何的例外規格，這會是預設的，也就是說可加可不加，加了會提升可讀性。

```cpp=
void func1(int a) noexcept {
    ...
}

void func2(int b) noexcept(false) {
    ...
}
```

總結
---

### 什麼是例外（Exception）？

- 例外是指程式執行過程中發生的異常狀況或錯誤事件，如除數為 0。
- 例外處理是指對這些異常狀況進行捕捉與處理的機制，避免程式異常終止。

### 基本例外處理語法

- 使用 `try` 區塊包著可能拋出例外的程式碼。
- 使用 `catch` 區塊捕捉並處理特定類型的例外。

語法：

```cpp=
try {
    // 可能拋出例外的程式碼
} 
catch (ExceptionType e) {
    // 例外處理程式碼
}
```

`throw` 關鍵字用於拋出例外：

```cpp
throw val;
```

### 標準例外類別

定義於 `<stdexcept>` or `<typeinfo>`。

分為兩大類：

- 邏輯錯誤（logic_error）：程式邏輯錯誤，可預先檢查。
- 執行期錯誤（runtime_error）：執行時非預期錯誤。

標準例外表：

| 例外類型     | 說明                                              |
| ------------------------ | ------------------------------------------------------------ |
| `std::logic_error`       | 邏輯錯誤。表示程式中存在不合理的邏輯問題，通常是可預先檢查出的錯誤。                           |
| `std::invalid_argument`  | 無效的參數。通常因傳遞了錯誤或非法的參數值而引發。屬於 `logic_error` 的子類。               |
| `std::domain_error`      | 數學定義域錯誤。當參數不在函數允許的數學定義域內時拋出，例如平方根的負數輸入。屬於 `logic_error` 的子類。 |
| `std::length_error`      | 長度錯誤。當容器長度超過其最大容量限制時拋出。屬於 `logic_error` 的子類。                 |
| `std::out_of_range`      | 超出範圍。當使用無效索引存取容器元素時拋出。屬於 `logic_error` 的子類。                  |
| `std::runtime_error`     | 執行期間錯誤。表示在執行階段發生非預期狀況，無法在編譯期間預測。                             |
| `std::range_error`       | 範圍錯誤。當數值運算結果超出有效範圍但仍合法（如浮點精度損失）時拋出。屬於 `runtime_error` 的子類。   |
| `std::overflow_error`    | 溢位錯誤。當數值計算超過資料型態的上限時拋出。屬於 `runtime_error` 的子類。               |
| `std::underflow_error`   | 下溢錯誤。當浮點數計算結果太接近零而無法正確表示時拋出。屬於 `runtime_error` 的子類。          |
| `std::bad_alloc`         | 記憶體配置失敗。當 `new` 無法配置足夠記憶體時拋出。屬於 `std::exception` 的子類。        |
| `std::bad_function_call` | 錯誤的函數呼叫。當呼叫尚未設定目標的 `std::function` 物件時拋出。                    |
| `std::bad_cast`          | 錯誤的型態轉換。當使用 `dynamic_cast` 進行不合法的轉型時拋出。                      |

註：每個標準例外都有 `what()` public 方法可取得錯誤訊息。

### 多重 catch 與 catch 所有例外

可以有多個 catch：

```cpp=
try {
    // 可能拋出多種例外
}
catch (type1 e) {
    // 處理 type1 例外
}
catch (type2 e) {
    // 處理 type2 例外
}
catch (...) {
    // 捕捉所有未匹配的例外
}
```

- `catch(...)` 用來捕捉所有未知例外。

### 以參考捕捉例外（catch by reference）

使用 `catch (ExceptionType& e)` 避免 copy，提高效率，也能捕捉多型例外。

### 例外規格（Exception Specification）

- noexcept：保證函數不會拋出例外。
- noexcept(false)：函數可能會拋出例外，為預設行為。

參考資料
---

[例外規格（Exception Specifications）](https://openhome.cc/Gossip/CppGossip/ExceptionSpecifications.html)

[How to Catch All Exceptions in C++? - GeeksforGeeks](https://www.geeksforgeeks.org/cpp/how-to-catch-all-exceptions-in-cpp/)

[Exception Handling in C++ - GeeksforGeeks](https://www.geeksforgeeks.org/exception-handling-c/)

[C++ 异常处理 | 菜鸟教程](https://www.runoob.com/cplusplus/cpp-exceptions-handling.html)