【C++ 筆記】例外處理(Exception Handling) - part 28

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

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

例外是什麼?

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

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

基本例外處理

try-catch

語法:

1
2
3
4
5
6
7
8
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 會偵測這個例外是什麼東西,如果符合的話就會去處理,如:

1
2
3
4
5
6
try {         
throw val
}
catch (ExceptionType e) {
// exception handling code
}

範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#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:

1
2
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

Image Source:GeeksForGeeks

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

例外類型說明
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() 方法提供這些標準例外的相關資訊。

以下是個範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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:

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

catch 多個例外

不只是 catch 一個例外而已,也可以多個例外:

1
2
3
4
5
6
7
8
9
10
11
12
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
}

以下是個範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#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(...) 即可捕捉所有的例外,延續上個範例,增加這個上去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#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,能減少這部分的效能開銷。除了這之外,主要拿來捕捉多型的例外。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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:

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

例外規格(Exception Specification)

  1. noexcept or noexcept(true):如其名,告訴函數保證不會引發例外。
  2. noexcept(false):表示可能會拋出例外。若未使用任何的例外規格,這會是預設的,也就是說可加可不加,加了會提升可讀性。
1
2
3
4
5
6
7
void func1(int a) noexcept {
...
}

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

總結

什麼是例外(Exception)?

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

基本例外處理語法

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

語法:

1
2
3
4
5
6
try {
// 可能拋出例外的程式碼
}
catch (ExceptionType e) {
// 例外處理程式碼
}

throw 關鍵字用於拋出例外:

1
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:

1
2
3
4
5
6
7
8
9
10
11
12
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)

How to Catch All Exceptions in C++? - GeeksforGeeks

Exception Handling in C++ - GeeksforGeeks

C++ 异常处理 | 菜鸟教程