例外是指存在于程式運行時的例外行為,這些行為超出了函式正常功能的范圍,當程式的某部分檢測到一個無法處理的問題時,就需要用到例外處理,
目錄
- 1. C語言中傳統的處理錯誤方式
- 2. C++中處理例外的方式
- 2.1 throw
- 2.2 try
- 3. 例外拋出和捕獲的規則
- 3.1 例外重新拋出
- 4. 例外安全
- 4.1 例外規范
- 5. 自定義例外體系
- 6. 標準例外
- 7. 例外優缺點
1. C語言中傳統的處理錯誤方式
終止程式:如assert,當發生錯誤時,直接終止程式,這樣的作法不友好,
回傳錯誤碼:如果函式體里發生錯誤時,將錯誤碼回傳給coder,需要去查找對應的錯誤,系統的庫函式介面就是通過把錯誤碼放到errno中,表示錯誤,
Windows下,使用perror列印全部錯誤:
for (int i = 0; i < 43; ++i)
{
cout << i << " : ";
perror(strerror(i));
cout << endl;
}
大部分情況下,C語言出現錯誤,都是使用的是回傳錯誤碼的方式處理,部分情況下使用終止程式來處理十分嚴重的錯誤,
2. C++中處理例外的方式
如果程式中含有可能引發例外的代碼,那么通常也需要有專門的代碼處理問題,如:程式的問題是輸入無效,則例外處理部分可能會要求用戶重新輸入正確的資料,
例外處理機制為程式中例外檢測和例外處理這兩部分的協作提供支持,C++中,例外處理包括:
- throw:例外檢測部分使用
throw來表示它遇到了無法處理的問題,此時就會拋例外; - catch:用于捕獲例外,可以有多個catch同時進行捕獲;
- try:try塊中的代碼拋出的例外通常會被一個或多個catch處理,因為catch處理例外,所以他們也被稱為例外處理代碼,
2.1 throw
程式的例外檢測部分使用throw拋出一個例外,throw后緊跟一個運算式,該運算式的型別就是拋出的例外型別,
一般來說,直接將例外拋出,交給后面的程式處理例外,不應該將例外資訊給直接輸出,
int main()
{
FILE* fp = fopen("a.txt", "a");
//拋出例外的型別為string型別
if (fp == nullptr)
throw string("請檢查檔案是否存在");
return 0;
}
2.2 try
try關鍵字后,緊跟著一個塊,這個塊中是花括號擴起來的陳述句序列,跟在try塊之后的是一個或多個catch子句;
catch子句包括三部分:關鍵字catch、括號內的物件宣告(例外宣告,例外型別,拋出例外的型別要和catch處理的例外型別相同),一個處理例外的代碼塊;
當選中某個catch子句處理例外后,執行與之對應的塊,catch一旦完成,程式跳轉到try陳述句最后一個catch之后的陳述句繼續執行,
int main()
{
try
{
FILE* fp = fopen("a.txt", "r");// 以只讀方式打開一個檔案
if (fp == nullptr)
throw string("請檢查檔案是否存在");
}
catch (const char* msg)// char*型別例外
{
cout << msg << endl;
}
catch (const string& msg)//string型別例外
{
cout << msg << endl;
}
catch (...)//... 代表可以捕獲任意型別的例外
{
cout << "出現了無法解決的例外" << endl;
}
return 0;
}
3. 例外拋出和捕獲的規則
- 例外是通過拋出物件而引起的,該物件的型別決定了應該匹配哪個
catch的處理代碼; - 例外處理部分
catch,是呼叫鏈中與該型別匹配且拋出例外位置最近的那一個; - 拋出例外物件后,會生成一個例外物件的拷貝,因為拋出的物件可能是一個臨時物件,所以會生成一個拷貝物件,該拷貝的臨時物件會在
catch結束后銷毀; catch(...)可捕獲任意型別例外,代表了不知道出現例外的錯誤是什么,也就無法進行解決;- 在例外的拋出和捕獲中,并不是型別的完全匹配,可以拋出派生類物件,使用基類捕獲(很重要),
在函式呼叫鏈中例外堆疊展開的匹配規則:
- 先檢查
throw是否在try塊內部,如果是則再查找匹配的catch陳述句,如果有匹配則呼叫catch的例外處理代碼; - 若沒有匹配的
catch例外處理代碼,則退出當前函式堆疊,繼續在呼叫函式堆疊中查找匹配catch; - 如果達到main函式堆疊中,仍沒有匹配,則終止程式,沿著呼叫堆疊查找匹配的
catch子句的程序稱為堆疊展開;所以一般情況下,都要在最后加一個catch(...)捕獲任意型別的例外,否則當有例外沒有被捕獲時,就會導致程式終止; - 找到匹配的catch子句后,會沿著catch之后的代碼繼續執行,
注意:
- throw可以拋出任意型別的例外,拋出的例外必須進行捕獲,否則程式就會終止;
- throw拋出例外后,若是在多個函式堆疊中呼叫時,會直接跳轉到有匹配的catch子句中,若沒有匹配的子句時,程式終止;
catch(...)可捕獲任意型別例外,
3.1 例外重新拋出
有可能單個的例外不能完全處理一個例外,則在進行一些處理后,希望再給外層的呼叫鏈函式來處理,catch則可以通過重新拋出例外將例外傳遞給更上層的函式處理;
double Div(int a, int b)
{
if (b == 0)
throw string("發生了除0錯誤");
return a / b;
}
void func()
{
int *p = new int(10);
int b = 0;
cout << Div(*p, b);
delete p;
}
int main()
{
try
{
func();
}
catch (const string& s)
{
cout << s << endl;
}
return 0;
}
上述代碼中,會出現記憶體泄漏,throw拋出的例外,直接跳轉到main函式堆疊中,則會導致func中,申請的空間沒有釋放,造成記憶體泄漏,則需要對該例外進行重新捕獲,并且釋放該空間,避免記憶體泄漏,
這樣修改就不會存在記憶體泄漏:
void func()
{
int *p = new int(10);
try
{
int b = 0;
cout << Div(*p, b);
}
catch (...)
{
delete p;
throw;
}
delete p;
}
4. 例外安全
例外安全:例外導致的安全問題,
例外中斷了程式的正常流程,例外發生時,呼叫者請求的一部分計算可能已經完成了,另一部可能還沒完成,通常情況下,略過部分程式意味著某些物件處理到一般就戛然而止了,從而導致物件處于無效或未完成的狀態,或者資源沒有被正常釋放,
那些在例外發生期間正確執行了“清理”作業的程式被稱為例外安全的代碼,
注意:
- 建構式完成物件的構造和初始化,最好不要在建構式中拋例外,否則可能導致物件不完整或者沒有完全初始化;
- 解構式主要完成資源的清理,最好不要在解構式中拋出例外,否則可能導致資源泄漏;
- C++中經常會導致資源泄漏的問題,如new 和 delete中拋出例外,導致記憶體泄漏,lock和unlock之間拋出遺產,導致死鎖,C++經常使用RAII來解決上述問題;
4.1 例外規范
- 例外規則說明說明的目的是為了讓函式使用者知道該函式可能拋出什么例外,在函式后面接throw,列出這個函式可能拋出的所有例外型別;
void func() throw(string, char, char*);//可拋出三種型別的例外
void* operator new(size_t) throw(bad_alloc);//只會拋bad_alloc例外
- 函式后面接throw(),表示不會拋出例外;
void func() throw();//不拋例外
void* operator new(size_t) throw();//不拋例外
- 如果沒有例外介面宣告,則可以拋任意型別的例外,
5. 自定義例外體系
自定義例外的體系,一般情況下,拋出派生類的例外,由基類捕獲,這樣在不同的派生類中,可以拋出許多不同的例外,而且具有相同的呼叫方式(由基類呼叫),避免呼叫混亂,方便管理,
class Exception
{
public:
Exception(const char* msg)
:_errmsg(msg)
{}
virtual string what() = 0;//純虛函式,介面類
string _errmsg;
};
class NetException : public Exception
{
public:
NetException(const char* msg)
:Exception(msg)
{}
virtual string what()
{
return "網路錯誤" + _errmsg;
}
};
class SqlException : public Exception
{
public:
SqlException(const char* msg)
:Exception(msg)
{}
virtual string what()
{
return "資料庫錯誤" + _errmsg;
}
};
那么在捕獲的時候,只需要捕獲基類的例外即可:
void Func()
{
if (rand() % 33 == 0)
throw SqlException("資料庫啟動出錯");
else if(rand() % 17 == 0)
throw NetException("網路連接出錯");
}
int main()
{
for (int i = 0; i < 188; ++i)
{
try
{
Func();
}
catch (Exception& e)//捕獲基類 即可
{
cout << e.what() << endl;
}
}
return 0;
}
6. 標準例外
stdexcept頭檔案定義了幾種常用的例外類:
| 例外 | 描述 |
|---|---|
| logic_error | 程式邏輯出錯,一般通過讀取代碼來解決 |
| domain_error | 邏輯錯誤,引數 對應的結果值不存在 |
| invalid_argument | 邏輯錯誤:無效引數 |
| length_error | 邏輯錯誤:創建一個超出該型別最大長度的物件 |
| runtime_error | 運行時才能檢測出的問題,一般無法通過讀取代碼來解決 |
| range_error | 運行時出錯:生成的結果超出了有意義的值域范圍 |
| overflow_error | 計算上溢 |
| underflow_error | 計算下溢位 |
new頭檔案中,定義了bad_alloc例外型別:
| 例外 | 描述 |
|---|---|
| bad_alloc | new分配記憶體失敗時導致的例外 |
typeinfo頭檔案定義了兩個例外型別:
| 例外 | 描述 |
|---|---|
| bad_typeid | 對null指標的的typeid拋出例外 |
| bad_cast | 動態轉換失敗時引發例外 |
7. 例外優缺點
優點:
- 清晰的包含錯誤資訊;
- 如果有越界問題時,可以很方便的處理;
- 多層呼叫時,里層發生錯誤,不會層層呼叫,最外層可直接捕獲;
- 一些第三方庫也是使用例外,使用例外時可以很方便使用這些庫:如boost
缺點:
- 例外會導致執行流跳轉,分析程式時會有一些問題;
- C++中沒有GC,例外可能會導致資源泄漏的風險;
- C++庫中定義的例外體系,可用性不高,一般自己定義;
- C++可以拋任意型別的例外,則需要對例外最很好的規范管理,否則就會非常混亂,所以一般定義出繼承體系下的例外規范,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/264795.html
標籤:其他
上一篇:加密與解密原理
