C++中的異常實(shí)例詳解
1. 異常
異常: 異常是面向?qū)ο笈c法處理錯(cuò)誤的一種方式
1.1 C語言中處理錯(cuò)誤的方式
- 返回錯(cuò)誤碼 (很多API接口都會(huì)把錯(cuò)誤碼放到errno當(dāng)中)
- 終止程序 (assert終止、除零錯(cuò)誤、段錯(cuò)誤) [產(chǎn)生信號(hào)去終止進(jìn)程]
- C標(biāo)準(zhǔn)庫中setjump和longjump組合
1.2 C語言處理錯(cuò)誤方式的缺陷
- 使用錯(cuò)誤碼時(shí),還需要查看錯(cuò)誤碼表,去找每個(gè)錯(cuò)誤碼的含義
- 當(dāng)一個(gè)函數(shù)是通過返回值去輸出數(shù)據(jù),那么發(fā)生錯(cuò)誤的時(shí)候很難處理 (不好區(qū)設(shè)置發(fā)生錯(cuò)誤時(shí)返回什么)
- 如果調(diào)用的函數(shù)棧很深時(shí),使用錯(cuò)誤碼去一層層返回時(shí),處理起來很麻煩
//第2點(diǎn) T& operator[](int index) { //問題: 當(dāng)訪問的下標(biāo)index超出容器的范圍,此時(shí)該返回什么值? //解決方案: 可以再傳一個(gè)參數(shù),用于獲取真正的結(jié)果(當(dāng)輸出型參數(shù)), 返回值就用于判斷是否正確的返回了. //實(shí)際上還是很麻煩 }
2. C++異常
2.1 異常相關(guān)關(guān)鍵字
- try: try塊中放置可能會(huì)拋出異常的代碼,這段代碼被稱為保護(hù)代碼
- catch: catch用于捕獲異常,catch塊中寫處理異常的代碼。它跟在try的后面,1個(gè)try后面可以跟多個(gè)catch
- throw: throw用于拋出異常
try { //保護(hù)代碼(可能會(huì)拋出異常的代碼) }catch(ExceptionType e1) { //處理異常 }catch(ExceptionType e2) { //處理異常 }catch(ExceptionType eN) { //處理異常 }
2.2 異常的使用
2.2.1 異常的拋出和匹配原則
- 異常是通過拋出對(duì)象而引發(fā)的,該對(duì)象的類型決定了應(yīng)該匹配到哪個(gè)catch塊
- 異常的拋出會(huì)去匹配在整個(gè)調(diào)用鏈中與該對(duì)象類型相匹配且距離最近的那個(gè)
- 當(dāng)異常拋出后,執(zhí)行流會(huì)直接跳轉(zhuǎn)到整個(gè)調(diào)用鏈當(dāng)中能catch到異常的位置處,不能catch的地方就不會(huì)執(zhí)行了,所以這可能導(dǎo)致內(nèi)存泄漏、文件流沒關(guān)閉、鎖未釋放**等情況。(free、fclose、unlock未執(zhí)行)
- catch(…)可以用來捕獲任意類型對(duì)象的異常,一般用于表示"未知異常"或被用作異常的重新拋出時(shí)的接收catch塊
- 在拋出與捕獲的類型匹配上并不全都是類型相同才能匹配。我們可以使用基類去捕獲 —> 拋出的派生類的對(duì)象。 //會(huì)在下面具體講解
· 原則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,則終止程序。
棧展開:上述的這個(gè)沿著調(diào)用鏈去逐個(gè)棧中查找匹配的catch語句的過程就是棧展開。
//代碼演示:f3()中拋出異常,該異常會(huì)被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; }
注意:
拋出異常對(duì)象后,會(huì)生成一個(gè)異常對(duì)象的拷貝(可能是一個(gè)臨時(shí)對(duì)象),這個(gè)拷貝的臨時(shí)對(duì)象在被catch后會(huì)被銷毀。異常的執(zhí)行流執(zhí)行順序:從throw拋出異常處跳轉(zhuǎn)到調(diào)用鏈中能夠匹配的catch語句中;然后執(zhí)行catch塊中的代碼;catch執(zhí)行完畢后在當(dāng)前函數(shù)棧中順序執(zhí)行。(整個(gè)調(diào)用鏈中沒有匹配的就終止程序)
2.3 異常的重新拋出
可能會(huì)存在單個(gè)catch不能完全處理一個(gè)異常的情況,在經(jīng)過一些校正處理后,我們希望將該異常交給外層調(diào)用鏈中的函數(shù)來處理,此時(shí)我們可以通過在catch中重新拋出異常的方式把異常傳遞給調(diào)用鏈的上層函數(shù)處理。
//在SecondThrowException()函數(shù)中我們要delete[]動(dòng)態(tài)開辟(new出來)的空間, //如果不使用異常的重新拋出的話,就會(huì)造成內(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 自定義異常體系
自定義異常體系實(shí)際上就是自己定義的一套異常管理體系,很多公司當(dāng)中都會(huì)自定義自己的異常體系以便于規(guī)范的進(jìn)行異常管理。它主要用到了我們?cè)谏厦嫠f的一條規(guī)則: 我們只需要拋出派生類對(duì)象,然后捕獲基類對(duì)象就可以了。這樣的拋出與捕獲方式非常便于異常的處理。
class MyException { public: MyException(string errmsg, int id) :_errmsg(errmsg), _id(id) {} virtual string what() const = 0; //必須放到public下才能讓類外定義的成員訪問到 protected: int _id; //錯(cuò)誤碼 string _errmsg; //存放錯(cuò)誤信息 //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 { //拋出任意的派生類對(duì)象 throw SqlException("sql open failed", 10); } catch (const MyException& e) //只需要捕獲基類對(duì)象 { cout << e.what() << endl; //這里實(shí)際上完成了一個(gè)多態(tài) } catch (...) //走到這里說明出現(xiàn)未知異常 { cout << "Unknown Exception!" << endl; } return 0; }
2.5 異常安全
- 最好不要在構(gòu)造函數(shù)中拋異常,構(gòu)造函數(shù)是完成對(duì)象的構(gòu)造和初始化的,在里面拋異??赡軙?huì)導(dǎo)致對(duì)象構(gòu)造不完整或沒有完全初始化。 (可能會(huì)造成內(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ù)不會(huì)拋異常 void func2() noexcept; //等價(jià)于throw() 表示該函數(shù)不會(huì)拋異常 void func3() throw(std::bad_alloc); //表示該函數(shù)只會(huì)拋出bad_alloc的異常 void func4() throw(int, double, string); //表示該函數(shù)會(huì)拋出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ù)期的異常時(shí)非常有用。 |
std::bad_typeid | 該異??梢酝ㄟ^ typeid 拋出。 |
std::logic_error | 理論上可以通過讀取代碼來檢測(cè)到的異常。 |
std::domain_error | 當(dāng)使用了一個(gè)無效的數(shù)學(xué)域時(shí),會(huì)拋出該異常。 |
std::invalid_argument | 當(dāng)使用了無效的參數(shù)時(shí),會(huì)拋出該異常。 |
std::length_error | 當(dāng)創(chuàng)建了太長的 std::string 時(shí),會(huì)拋出該異常。 |
std::out_of_range | 該異??梢酝ㄟ^方法拋出,例如 std::vector 和 std::bitset<>::operator。 |
std::runtime_error | 理論上不可以通過讀取代碼來檢測(cè)到的異常。 |
std::overflow_error | 當(dāng)發(fā)生數(shù)學(xué)上溢時(shí),會(huì)拋出該異常。 |
std::range_error | 當(dāng)嘗試存儲(chǔ)超出范圍的值時(shí),會(huì)拋出該異常。 |
std::underflow_error | 當(dāng)發(fā)生數(shù)學(xué)下溢時(shí),會(huì)拋出該異常。 |
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)缺點(diǎn)
2.8.1 優(yōu)點(diǎn)
- 清晰的顯示出錯(cuò)誤信息
- 可以很好地解決返回值需要返回有效數(shù)據(jù)的函數(shù) //如T& operator[ ] (int index)
- 在多層函數(shù)調(diào)用時(shí)發(fā)生錯(cuò)誤,可以用直接在外層進(jìn)行捕獲異常
- 異常在很多第三方庫中也有使用 //boost、gtest、gmock
2.8.2 缺點(diǎn)
- 異常會(huì)導(dǎo)致執(zhí)行流跳轉(zhuǎn),這會(huì)使得調(diào)試分析程序時(shí)更加困難
- C++沒有GC (垃圾回收機(jī)制),使用異常時(shí)可能導(dǎo)致資源泄露等異常安全問題。
- C++標(biāo)準(zhǔn)庫的異常體系不實(shí)用
- C++的允許拋出任意類型的異常,在項(xiàng)目中不進(jìn)行規(guī)范管理的話,會(huì)十分混亂。 //一般需要定義一套繼承體系的異常規(guī)范
總結(jié)
到此這篇關(guān)于C++異常的文章就介紹到這了,更多相關(guān)C++異常內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
windows下vscode環(huán)境c++利用matplotlibcpp繪圖
本文主要介紹了windows下vscode環(huán)境c++利用matplotlibcpp繪圖,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02c++語言中虛函數(shù)實(shí)現(xiàn)多態(tài)的原理詳解
這篇文章主要給大家介紹了關(guān)于c++語言中虛函數(shù)實(shí)現(xiàn)多態(tài)的原理的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用c++語言具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05MFC控件之CListCtrl的應(yīng)用實(shí)例教程
這篇文章主要介紹了MFC控件中CListCtrl的應(yīng)用方法,包括了針對(duì)表格的一些操作,是MFC中比較重要的一個(gè)控件類,需要的朋友可以參考下2014-08-08