C++11中異常處理機(jī)制詳解
一、異常的引入
傳統(tǒng)的C語言處理異常的方式有兩種:
1.終止程序:使用assert斷言語句,如果發(fā)生內(nèi)存錯誤等,比如內(nèi)存泄漏或者除0錯誤,都會直接終止程序。
2.返回錯誤碼:通過錯誤碼判斷發(fā)生的異常的類型是什么,如系統(tǒng)的很多庫的接口程序通過把錯誤碼放到errno中,表示錯誤。
在實際中的C語言程序基本都是通過返回錯誤碼的方式來處理錯誤的,部分情況下使用終止程序來處理比較嚴(yán)重的錯誤。
二、C++異常的關(guān)鍵字
目前市面上的大部分的主流語言都是使用異常機(jī)制來處理錯誤的,當(dāng)一個函數(shù)發(fā)現(xiàn)自己無法處理一些錯誤的時候會進(jìn)行拋異常處理,讓函數(shù)直接跳轉(zhuǎn)到捕獲異常的地方去處理異常。
下面介紹C++處理異常的幾個關(guān)鍵字:
throw:當(dāng)問題出現(xiàn)的時候,程序會拋出一個異常,這是通過throw關(guān)鍵字來完成的。
catch:通過異常處捕獲異常,catch塊主要用于處理異常,或者執(zhí)行一些其他的操作。
try:try塊中的代碼標(biāo)識將被激活的特定異常,它的后面一般會跟一個catch塊。
三、異常的拋出與處理規(guī)則
double Disvision(int a, int b) { if (b == 0) { throw "Disvision by zero condition"; } else { return ((double)a / (double)b); } } void Func() { int len, time; cin >> len >> time; cout << Disvision(len, time) << endl; } int main() { try { Func(); } catch(const char* errmsg) { cout << errmsg << endl; } catch (...) { cout << "unkown exception" << endl; } return 0; }
當(dāng)向time傳入的值為0的時候,調(diào)用throw拋出異常,catch會捕獲到該異常進(jìn)行處理。
1.異常是通過拋出對象而引發(fā)的,該對象的類型決定了應(yīng)該激活哪一個catch的處理代碼。
2.被選中的處理代碼是調(diào)用鏈中與該對象類型匹配且離拋出異常位置最近的一個。
在這段程序中,有一個調(diào)用鏈:main()->Func()->Disvision(),在Disvision中拋出異常就會沿著調(diào)用鏈尋找能捕獲異常的catch來執(zhí)行。假設(shè)Func()中有一個與main()中一模一樣的catch函數(shù),那么除0的異常就會優(yōu)先調(diào)用Func()中的catch,因為它離著最近。
3.catch的異常其實是拋出對象的拷貝,因為真正拋出的對象在出作用域的時候就已經(jīng)被銷毀了,拷貝的對象在catch成功之后也會自動銷毀。
4.catch(…)可以捕獲任意類型的異常,但問題是不知道異常錯誤是什么。
5.實際中拋出和捕獲的匹配原則有一個例外,并不都是類型完全匹配,可以拋出的派生類對象,使用基類捕獲。(要引入多態(tài))
6.當(dāng)catch異常之后,會沿著catch后的子句繼續(xù)執(zhí)行,類似goto,算是異常的一個缺陷。
四、異常缺陷的處理
double Disvision(int a, int b) { if (b == 0) { throw "Disvision by zero condition"; } else { return ((double)a / (double)b); } } void Func() { int* array = new int[10]; int len, time; cin >> len >> time; cout << Disvision(len, time) << endl; delete[] array;//一旦拋出異常,這里就不會被執(zhí)行 } int main() { try { Func(); } catch(const char* errmsg) { cout << errmsg << endl; } catch (...) { cout << "unkown exception" << endl; } return 0; }
對于這段代碼而言,一旦Division拋出異常就會立刻執(zhí)行主函數(shù)中的catch,delete釋放內(nèi)存就不會被執(zhí)行。因此我們需要對這種情況進(jìn)行處理,即在Func中也捕獲一次異常,但是不對異常進(jìn)行處理,只是釋放空間:
void Func() { int* array = new int[10]; int len, time; cin >> len >> time; try { cout << Disvision(len, time) << endl; } catch (const char* errmsg) { cout << "釋放空間" << endl; delete[] array; throw; } }
在釋放空間之后,直接調(diào)用throw表示將捕獲到的異常再拋出去。
五、自定義異常體系
class Exception { public: Exception(const string& errmsg, int id) :_errmsg(errmsg) , _id(id) {} virtual string what() const { return _errmsg; } protected: string _errmsg; int _id; }; class SqlException : public Exception { public: SqlException(const string& errmsg, int id, const string& sql) :Exception(errmsg, id) , _sql(sql) {} virtual string what() const { string str = "SqlException:"; str += _errmsg; str += "->"; str += _sql; return str; } private: const string _sql; }; class CacheException : public Exception { public: CacheException(const string& errmsg, int id) :Exception(errmsg, id) {} virtual string what() const { string str = "CacheException:"; str += _errmsg; return str; } }; class HttpServerException : public Exception { public: HttpServerException(const string& errmsg, int id, const string& type) :Exception(errmsg, id) , _type(type) {} virtual string what() const { string str = "HttpServerException:"; str += _type; str += ":"; str += _errmsg; return str; } private: const string _type; }; void SQLMgr() { srand(time(0)); if (rand() % 7 == 0) { throw SqlException("權(quán)限不足", 100, "select * from name = '張三'"); } //throw "xxxxxx"; } void CacheMgr() { srand(time(0)); if (rand() % 5 == 0) { throw CacheException("權(quán)限不足", 100); } else if (rand() % 6 == 0) { throw CacheException("數(shù)據(jù)不存在", 101); } SQLMgr(); } void HttpServer() { // ... srand(time(0)); if (rand() % 3 == 0) { throw HttpServerException("請求資源不存在", 100, "get"); } else if (rand() % 4 == 0) { throw HttpServerException("權(quán)限不足", 101, "post"); } CacheMgr(); } void ServerStart() { while (1) { this_thread::sleep_for(chrono::seconds(1));//休眠1s try { HttpServer(); } catch (const Exception& e) // 這里捕獲父類對象就可以 { // 多態(tài) cout << e.what() << endl; } catch (...) { cout << "Unkown Exception" << endl; } } } int main() { ServerStart(); return 0; }
這里使用隨機(jī)數(shù)進(jìn)行模擬異常的拋出類型,我們使用父類捕獲異常,拋出子類異常,使用多態(tài)調(diào)用子類中的what()函數(shù)。
六、異常規(guī)范
1.異常規(guī)格說明的目的是為了讓函數(shù)使用者知道??梢栽诤瘮?shù)的后面接throw類(類型),列出這個函數(shù)可能拋的所有異常類型。
2.函數(shù)后面接throw(),表示函數(shù)不拋異常。
3.若無異常接口聲明,則此函數(shù)可以拋擲任何類型的異常。
void func() throw(A, B, C, D);//只會拋A/B/C/D中的某種類型的異常 void* operator new(size_t size) throw(bad alloc);//這里表示這個函數(shù)只會拋出bad_alloc的異常 void* operator new(size_t size, void* ptr) throw();//這個函數(shù)不會拋異常
在C++11中,引入了noexception
bool Compare(int x, int y) noexcept(noexcept(x > y)) //C++11 { return x > y;//表示如果x > y不發(fā)生異常,則Compare函數(shù)不會發(fā)生異常。 }
七、異常安全
構(gòu)造函數(shù)完成對象的初始化,最好不要在構(gòu)造函數(shù)中拋異常,否則可能導(dǎo)致對象不完整,或沒有完全初始化。
析構(gòu)函數(shù)主要完成資源的清理,最好不要在析構(gòu)函數(shù)中拋異常,否則可能導(dǎo)致資源泄漏(內(nèi)存泄漏,句柄未關(guān)閉)。
C++異常經(jīng)常會導(dǎo)致資源泄漏的問題,比如在new和delete中拋出了異常,導(dǎo)致內(nèi)存泄漏,在lock和unlock之間拋出了異常導(dǎo)致死鎖,C++經(jīng)常使用RAII來解決以上問題,關(guān)于RAII我們在智能指針中講解。
八、異常的優(yōu)缺點
1.優(yōu)點
1.相比錯誤碼,更加清晰展示出錯信息。
2.很多庫中包含異常,boost,gtest,gmock等常用的庫,使用它們也需要異常。
3.部分函數(shù)使用異常更好處理,比如構(gòu)造函數(shù)沒有返回值,不方便使用錯誤碼方式處理,比如T&operator這樣的函數(shù),只有一個返回值,如果pos越界了只能使用異?;蛘呓K止程序處理,沒辦法通過返回值表示錯誤。
2.缺點
1.異常類似goto,會導(dǎo)致程序的執(zhí)行流亂跳,并且非?;靵y。
2.C++沒有垃圾回收機(jī)制,可能導(dǎo)致內(nèi)存泄漏。
3.各個公司的異常體系不同,有一定的學(xué)習(xí)成本。
到此這篇關(guān)于C++11中異常處理機(jī)制詳解的文章就介紹到這了,更多相關(guān)C++11異常處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用設(shè)計模式中的單例模式來實現(xiàn)C++的boost庫
這篇文章主要介紹了使用設(shè)計模式中的單例模式來實現(xiàn)C++的boost庫的方法,其中作者對線程安全格外強(qiáng)調(diào),需要的朋友可以參考下2016-03-03visual?studio?將編譯后的dll等文件自動復(fù)制到指定目錄的方法
這篇文章主要介紹了visual?studio?將編譯后的dll等文件自動復(fù)制到指定目錄,本文給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-03-03C/C++實現(xiàn)矩陣的轉(zhuǎn)置(示例代碼)
C/C++實現(xiàn)矩陣的轉(zhuǎn)置(示例代碼)需要的朋友可以過來參考下,希望對大家有所幫助2013-10-10