C++中的異常實例詳解
1. 異常
異常: 異常是面向?qū)ο笈c法處理錯誤的一種方式
1.1 C語言中處理錯誤的方式
- 返回錯誤碼 (很多API接口都會把錯誤碼放到errno當(dāng)中)
- 終止程序 (assert終止、除零錯誤、段錯誤) [產(chǎn)生信號去終止進(jìn)程]
- C標(biāo)準(zhǔn)庫中setjump和longjump組合
1.2 C語言處理錯誤方式的缺陷
- 使用錯誤碼時,還需要查看錯誤碼表,去找每個錯誤碼的含義
- 當(dāng)一個函數(shù)是通過返回值去輸出數(shù)據(jù),那么發(fā)生錯誤的時候很難處理 (不好區(qū)設(shè)置發(fā)生錯誤時返回什么)
- 如果調(diào)用的函數(shù)棧很深時,使用錯誤碼去一層層返回時,處理起來很麻煩
//第2點
T& operator[](int index)
{
//問題: 當(dāng)訪問的下標(biāo)index超出容器的范圍,此時該返回什么值?
//解決方案: 可以再傳一個參數(shù),用于獲取真正的結(jié)果(當(dāng)輸出型參數(shù)), 返回值就用于判斷是否正確的返回了.
//實際上還是很麻煩
}
2. C++異常
2.1 異常相關(guān)關(guān)鍵字
- try: try塊中放置可能會拋出異常的代碼,這段代碼被稱為保護(hù)代碼
- catch: catch用于捕獲異常,catch塊中寫處理異常的代碼。它跟在try的后面,1個try后面可以跟多個catch
- throw: throw用于拋出異常
try
{
//保護(hù)代碼(可能會拋出異常的代碼)
}catch(ExceptionType e1)
{
//處理異常
}catch(ExceptionType e2)
{
//處理異常
}catch(ExceptionType eN)
{
//處理異常
}
2.2 異常的使用
2.2.1 異常的拋出和匹配原則
- 異常是通過拋出對象而引發(fā)的,該對象的類型決定了應(yīng)該匹配到哪個catch塊
- 異常的拋出會去匹配在整個調(diào)用鏈中與該對象類型相匹配且距離最近的那個
- 當(dāng)異常拋出后,執(zhí)行流會直接跳轉(zhuǎn)到整個調(diào)用鏈當(dāng)中能catch到異常的位置處,不能catch的地方就不會執(zhí)行了,所以這可能導(dǎo)致內(nèi)存泄漏、文件流沒關(guān)閉、鎖未釋放**等情況。(free、fclose、unlock未執(zhí)行)
- catch(…)可以用來捕獲任意類型對象的異常,一般用于表示"未知異常"或被用作異常的重新拋出時的接收catch塊
- 在拋出與捕獲的類型匹配上并不全都是類型相同才能匹配。我們可以使用基類去捕獲 —> 拋出的派生類的對象。 //會在下面具體講解
· 原則2:函數(shù)調(diào)用鏈中的異常 - 棧展開的匹配原則
- 查看throw是否在try的內(nèi)部,如果存在能匹配到的catch語句,就跳轉(zhuǎn)到catch中進(jìn)行異常的處理。
- 如果沒有匹配的catch就退出當(dāng)前棧,去查找調(diào)用鏈當(dāng)中的能夠匹配到的catch
- 如果在main函數(shù)中都沒有找到匹配的catch,則終止程序。
棧展開:上述的這個沿著調(diào)用鏈去逐個棧中查找匹配的catch語句的過程就是棧展開。

//代碼演示:f3()中拋出異常,該異常會被f1()捕捉,而在main函數(shù)中的catch是無法捕捉到的
void f3()
{
throw 123;
}
void f2()
{
f3();
}
void f1()
{
try
{
f2();
cout << "f1() not catched Exception!" << endl;
}catch(int& err)
{
cout << "f1()-err: " << err << endl;
}
}
int main()
{
try
{
f1();
cout << "main not catched Exception!" << endl;
}catch(int& err)
{
cout << "main-err: " << err << endl;
}
return 0;
}

注意:
拋出異常對象后,會生成一個異常對象的拷貝(可能是一個臨時對象),這個拷貝的臨時對象在被catch后會被銷毀。異常的執(zhí)行流執(zhí)行順序:從throw拋出異常處跳轉(zhuǎn)到調(diào)用鏈中能夠匹配的catch語句中;然后執(zhí)行catch塊中的代碼;catch執(zhí)行完畢后在當(dāng)前函數(shù)棧中順序執(zhí)行。(整個調(diào)用鏈中沒有匹配的就終止程序)
2.3 異常的重新拋出
可能會存在單個catch不能完全處理一個異常的情況,在經(jīng)過一些校正處理后,我們希望將該異常交給外層調(diào)用鏈中的函數(shù)來處理,此時我們可以通過在catch中重新拋出異常的方式把異常傳遞給調(diào)用鏈的上層函數(shù)處理。
//在SecondThrowException()函數(shù)中我們要delete[]動態(tài)開辟(new出來)的空間,
//如果不使用異常的重新拋出的話,就會造成內(nèi)存泄漏問題 (也可以使用RAII)
void FirstThrowException()
{
throw "First throw a exception!";
}
void SecondThrowException()
{
int* arr = new int[10];
try
{
FirstThrowException();
}catch(...)
{
cout << "Delete[] arr Success!" << endl;
delete[] arr;
throw;
}
}
void SoluteException()
{
try
{
SecondThrowException();
}catch(const char* err)
{
cout << err << endl;
}
}
int main()
{
SoluteException();
return 0;
}

2.4 自定義異常體系
自定義異常體系實際上就是自己定義的一套異常管理體系,很多公司當(dāng)中都會自定義自己的異常體系以便于規(guī)范的進(jìn)行異常管理。它主要用到了我們在上面所說的一條規(guī)則: 我們只需要拋出派生類對象,然后捕獲基類對象就可以了。這樣的拋出與捕獲方式非常便于異常的處理。
class MyException
{
public:
MyException(string errmsg, int id)
:_errmsg(errmsg),
_id(id)
{}
virtual string what() const = 0; //必須放到public下才能讓類外定義的成員訪問到
protected:
int _id; //錯誤碼
string _errmsg; //存放錯誤信息
//list<StackInfo> _traceStack; //存放調(diào)用鏈的信息
//...
};
class CacheException : public MyException
{
public:
CacheException(string errmsg, int id)
:MyException(errmsg, id)
{}
virtual string what() const
{
return "CacheException!: " + _errmsg;
}
};
class NetworkException : public MyException
{
public:
NetworkException(string errmsg, int id)
:MyException(errmsg, id)
{}
virtual string what() const
{
return "NetworkException!: " + _errmsg;
}
};
class SqlException : public MyException
{
public:
SqlException(string errmsg, int id)
:MyException(errmsg, id)
{}
virtual string what() const
{
return "SqlException!: " + _errmsg;
}
};
int main()
{
try
{
//拋出任意的派生類對象
throw SqlException("sql open failed", 10);
}
catch (const MyException& e) //只需要捕獲基類對象
{
cout << e.what() << endl; //這里實際上完成了一個多態(tài)
}
catch (...) //走到這里說明出現(xiàn)未知異常
{
cout << "Unknown Exception!" << endl;
}
return 0;
}

2.5 異常安全
- 最好不要在構(gòu)造函數(shù)中拋異常,構(gòu)造函數(shù)是完成對象的構(gòu)造和初始化的,在里面拋異??赡軙?dǎo)致對象構(gòu)造不完整或沒有完全初始化。 (可能會造成內(nèi)存泄漏)
- 最好不要在析構(gòu)函數(shù)中拋異常,析構(gòu)函數(shù)是完成資源的清理工作的,在里面拋異??赡軐?dǎo)致資源沒清完就結(jié)束了函數(shù)調(diào)用,從而導(dǎo)致內(nèi)存泄漏、句柄未關(guān)閉等問題。
- 注意new、fopen、lock的使用
2.6 異常規(guī)范
異常規(guī)范的指定是為了讓使用者知道函數(shù)可能拋出哪些異常,用法:
void func1() throw(); //表示該函數(shù)不會拋異常 void func2() noexcept; //等價于throw() 表示該函數(shù)不會拋異常 void func3() throw(std::bad_alloc); //表示該函數(shù)只會拋出bad_alloc的異常 void func4() throw(int, double, string); //表示該函數(shù)會拋出int/double/string類型中的某種異常
2.7 C++標(biāo)準(zhǔn)庫的異常體系
C++提供了一系列標(biāo)準(zhǔn)的異常,定義在中,下面是這些異常的組織形式。

| 異常 | 描述 |
|---|---|
| std::exception | 該異常是所有標(biāo)準(zhǔn) C++ 異常的父類。 |
| std::bad_alloc | 該異常可以通過 new 拋出。 |
| std::bad_cast | 該異??梢酝ㄟ^ dynamic_cast 拋出。 |
| std::bad_exception | 這在處理 C++ 程序中無法預(yù)期的異常時非常有用。 |
| std::bad_typeid | 該異??梢酝ㄟ^ typeid 拋出。 |
| std::logic_error | 理論上可以通過讀取代碼來檢測到的異常。 |
| std::domain_error | 當(dāng)使用了一個無效的數(shù)學(xué)域時,會拋出該異常。 |
| std::invalid_argument | 當(dāng)使用了無效的參數(shù)時,會拋出該異常。 |
| std::length_error | 當(dāng)創(chuàng)建了太長的 std::string 時,會拋出該異常。 |
| std::out_of_range | 該異??梢酝ㄟ^方法拋出,例如 std::vector 和 std::bitset<>::operator。 |
| std::runtime_error | 理論上不可以通過讀取代碼來檢測到的異常。 |
| std::overflow_error | 當(dāng)發(fā)生數(shù)學(xué)上溢時,會拋出該異常。 |
| std::range_error | 當(dāng)嘗試存儲超出范圍的值時,會拋出該異常。 |
| std::underflow_error | 當(dāng)發(fā)生數(shù)學(xué)下溢時,會拋出該異常。 |
int main()
{
try
{
vector<int> v(5);
v.at(5) = 0; //v.at(下標(biāo)) = v[下標(biāo)] + 拋異常
}
catch(const exception& e)
{
cout << e.what() << endl;
}
catch(...)
{
cout << "Unknown Exception!" << endl;
}
}
2.8 異常的優(yōu)缺點
2.8.1 優(yōu)點
- 清晰的顯示出錯誤信息
- 可以很好地解決返回值需要返回有效數(shù)據(jù)的函數(shù) //如T& operator[ ] (int index)
- 在多層函數(shù)調(diào)用時發(fā)生錯誤,可以用直接在外層進(jìn)行捕獲異常
- 異常在很多第三方庫中也有使用 //boost、gtest、gmock
2.8.2 缺點
- 異常會導(dǎo)致執(zhí)行流跳轉(zhuǎn),這會使得調(diào)試分析程序時更加困難
- C++沒有GC (垃圾回收機(jī)制),使用異常時可能導(dǎo)致資源泄露等異常安全問題。
- C++標(biāo)準(zhǔn)庫的異常體系不實用
- C++的允許拋出任意類型的異常,在項目中不進(jìn)行規(guī)范管理的話,會十分混亂。 //一般需要定義一套繼承體系的異常規(guī)范
總結(jié)
到此這篇關(guān)于C++異常的文章就介紹到這了,更多相關(guān)C++異常內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
windows下vscode環(huán)境c++利用matplotlibcpp繪圖
本文主要介紹了windows下vscode環(huán)境c++利用matplotlibcpp繪圖,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02
c++語言中虛函數(shù)實現(xiàn)多態(tài)的原理詳解
這篇文章主要給大家介紹了關(guān)于c++語言中虛函數(shù)實現(xiàn)多態(tài)的原理的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用c++語言具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05

