C++異常使用詳解(看這一篇就夠了)
一、異常的概念
異常是一種處理錯(cuò)誤的方式,當(dāng)一個(gè)函數(shù)發(fā)現(xiàn)自己無法處理的錯(cuò)誤時(shí)就可以拋出異常,讓函數(shù)的直接或間接的調(diào)用者處理這個(gè)錯(cuò)誤。
- throw:當(dāng)問題出現(xiàn)時(shí),程序會(huì)拋出一個(gè)異常。這是通過使用 throw 關(guān)鍵字來完成的。
- catch: 在你想要處理問題的地方,通過異常處理程序捕獲異常,catch 關(guān)鍵字用于捕獲異常,可以有多個(gè)catch進(jìn)行捕獲。
- try: try 塊中的代碼標(biāo)識(shí)將被激活的特定異常,它后面通常跟著一個(gè)或多個(gè) catch 塊。
語法:
try { // 保護(hù)的標(biāo)識(shí)代碼 } catch( ExceptionName e1 ) { // catch 塊 } catch( ExceptionName e2 ) { // catch 塊 } catch( ExceptionName eN ) { // catch 塊 }
二、異常的使用
1.異常的拋出和捕獲原則
1.異常是通過拋出對(duì)象而引發(fā)的,該對(duì)象的類型決定了應(yīng)該激活哪個(gè)catch的處理代碼。
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<vector> using namespace std; double Division(int a, int b) { //當(dāng)b == 0時(shí)拋出異常 if (b == 0) throw "Division by zero condition!"; //throw 1; else return ((double)a / (double)b); } void func() { int len, time; cin >> len >> time; cout << Division(len, time) << endl; } int main() { try { func(); } catch (const char* errmsg) { cout << errmsg << endl; } catch (const int errmsg) { cout << errmsg << endl; } return 0; }
當(dāng)拋出的異常時(shí)char* 類型時(shí)
當(dāng)拋出異常換成整形時(shí)
2.被選中的處理代碼是調(diào)用鏈中與該對(duì)象類型匹配且離拋出異常位置最近的那一個(gè)
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<vector> using namespace std; double Division(int a, int b) { try { //當(dāng)b == 0時(shí)拋出異常 if (b == 0) throw "Division by zero condition!"; else return ((double)a / (double)b); } catch(const char* errmsg) { cout << "Division:" << errmsg << endl; } } void func() { int len, time; cin >> len >> time; cout << Division(len, time) << endl; } int main() { try { func(); } catch (const char* errmsg) { cout << errmsg << endl; } catch (const int errmsg) { cout << errmsg << endl; } return 0; }
3.拋出異常對(duì)象后,會(huì)生成一個(gè)異常對(duì)象的拷貝,因?yàn)閽伋龅漠惓?duì)象可能是一個(gè)臨時(shí)對(duì)象,所以會(huì)生成一個(gè)拷貝對(duì)象,這個(gè)拷貝的臨時(shí)對(duì)象會(huì)在被catch以后銷毀。(這里的處理類似于函數(shù)的傳值返回)
4.catch(…)可以捕獲任意類型的異常,問題是不知道異常錯(cuò)誤是什么。
void fun1() { throw "錯(cuò)誤"; } int main() { try { fun1(); } catch(const int errmsg) { cout << errmsg; } catch (...) { cout << "未知錯(cuò)誤" << endl; } }
5.實(shí)際中拋出和捕獲的匹配原則有個(gè)例外,并不都是類型完全匹配,可以拋出的派生類對(duì)象,使用基類捕獲,這個(gè)在實(shí)際中非常實(shí)用,以后主要用到的也是這種方式
2.在函數(shù)調(diào)用鏈中異常棧展開匹配原則
1.首先檢查throw本身是否在try塊內(nèi)部,如果是再查找匹配的catch語句。如果有匹配的,則調(diào)到catch的地方進(jìn)行處理。
2.沒有匹配的catch則退出當(dāng)前函數(shù)棧,繼續(xù)在調(diào)用函數(shù)的棧中進(jìn)行查找匹配的catch。
3.如果到達(dá)main函數(shù)的棧,依舊沒有匹配的,則終止程序。上述這個(gè)沿著調(diào)用鏈查找匹配的catch子句的過程稱為棧展開。所以實(shí)際中我們最后都要加一個(gè)catch(…)捕獲任意類型的異常,否則當(dāng)有異常沒捕獲,程序就會(huì)直接終止。
4.找到匹配的catch子句并處理以后,會(huì)繼續(xù)沿著catch子句后面繼續(xù)執(zhí)行。
3.異常規(guī)范
1.異常規(guī)格說明的目的是為了讓函數(shù)使用者知道該函數(shù)可能拋出的異常有哪些。 可以在函數(shù)的
后面接throw(類型),列出這個(gè)函數(shù)可能拋擲的所有異常類型
2.函數(shù)的后面接throw(),表示函數(shù)不拋異常。
3.若無異常接口聲明,則此函數(shù)可以拋擲任何類型的異常。
// 這里表示這個(gè)函數(shù)會(huì)拋出A/B/C/D中的某種類型的異常 void fun() throw(A,B,C,D); // 這里表示這個(gè)函數(shù)只會(huì)拋出bad_alloc的異常 void* operator new (std::size_t size) throw (std::bad_alloc); // 這里表示這個(gè)函數(shù)不會(huì)拋出異常 void* operator new (std::size_t size, void* ptr) throw(); // C++11 中新增的noexcept,表示不會(huì)拋異常 thread() noexcept;
4.異常安全
- 構(gòu)造函數(shù)完成對(duì)象的構(gòu)造和初始化,最好不要在構(gòu)造函數(shù)中拋出異常,否則可能導(dǎo)致對(duì)象不完整或沒有完全初始化
- 析構(gòu)函數(shù)主要完成資源的清理,最好不要在析構(gòu)函數(shù)內(nèi)拋出異常,否則可能導(dǎo)致資源泄漏(內(nèi)存泄漏、句柄未關(guān)閉等)
- C++中異常經(jīng)常會(huì)導(dǎo)致資源泄漏的問題,比如在new和delete中拋出了異常,導(dǎo)致內(nèi)存泄漏,在lock和unlock之間拋出了異常導(dǎo)致死鎖,C++經(jīng)常使用RAII來解決以上問題。
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<vector> #include<windows.h> using namespace std; double Division(int a, int b) { //當(dāng)b == 0時(shí)拋出異常 if (b == 0) throw "Division by zero condition!"; else return ((double)a / (double)b); } void func() { int* array = new int[10]; int len, time; cin >> len >> time; cout << Division(len, time) << endl; cout << "delete []" << array << endl; delete[] array; } int main() { try { func(); } catch (const char* errmsg) { cout << errmsg << endl; } catch (const int errmsg) { cout << errmsg << endl; } return 0; }
正常情況,沒有異常
有異常
如果出現(xiàn)了在new和delete中間拋出異?;蛘咧虚g的某個(gè)函數(shù)拋出異常,會(huì)導(dǎo)致程序直接跳轉(zhuǎn)到catch的地方而無法實(shí)現(xiàn)delete,出現(xiàn)內(nèi)存泄漏
5.異常的重新拋出
有可能單個(gè)的catch不能完全處理一個(gè)異常,在進(jìn)行一些校正處理以后,希望再交給更外層的調(diào)用鏈函數(shù)來處理,catch則可以通過重新拋出將異常傳遞給更上層的函數(shù)進(jìn)行處理。
double Division(int a, int b) { // 當(dāng)b == 0時(shí)拋出異常 if (b == 0) { throw "Division by zero condition!"; } return (double)a / (double)b; } void Func() { // 這里可以看到如果發(fā)生除0錯(cuò)誤拋出異常,另外下面的array沒有得到釋放。 // 所以這里捕獲異常后并不處理異常,異常還是交給外面處理,這里捕獲了再重新拋出去。 int* array = new int[10]; try { int len, time; cin >> len >> time; cout << Division(len, time) << endl; } catch (...) { cout << "delete []" << array << endl; delete[] array; throw; // 重新拋出異常,且內(nèi)容不變 } // ... cout << "delete []" << array << endl; delete[] array; } int main() { try { Func(); } catch (const char* errmsg) // 捕獲Func重新拋出的異常 { cout << errmsg << endl; } return 0; }
三、異常的優(yōu)缺點(diǎn)
異常的優(yōu)點(diǎn)
- 異常對(duì)象定義好了,相比錯(cuò)誤碼的方式可以清晰準(zhǔn)確的展示出錯(cuò)誤的各種信息,甚至可以包含堆棧調(diào)用的信息,這樣可以幫助更好的定位程序的bug。
- 返回錯(cuò)誤碼的傳統(tǒng)方式有個(gè)很大的問題就是,在函數(shù)調(diào)用鏈中,深層的函數(shù)返回了錯(cuò)誤,那么我們得層層返回錯(cuò)誤,最外層才能拿到錯(cuò)誤.
- 很多的第三方庫都包含異常,比如boost、gtest、gmock等等常用的庫,那么我們使用它們也需要使用異常。
- 很多測(cè)試框架都使用異常,這樣能更好的使用單元測(cè)試等進(jìn)行白盒的測(cè)試。
- 部分函數(shù)使用異常更好處理,比如構(gòu)造函數(shù)沒有返回值,不方便使用錯(cuò)誤碼方式處理。比如T&operator這樣的函數(shù),如果pos越界了只能使用異?;蛘呓K止程序處理,沒辦法通過返回值表示錯(cuò)誤。
異常的缺點(diǎn)
- 異常會(huì)導(dǎo)致程序的執(zhí)行流亂跳,并且非常的混亂,并且是運(yùn)行時(shí)出錯(cuò)拋異常就會(huì)亂跳。這會(huì)導(dǎo)致我們跟蹤調(diào)試時(shí)以及分析程序時(shí),比較困難。
- 異常會(huì)有一些性能的開銷。當(dāng)然在現(xiàn)代硬件速度很快的情況下,這個(gè)影響基本忽略不計(jì)。
- C++沒有垃圾回收機(jī)制,資源需要自己管理。有了異常非常容易導(dǎo)致內(nèi)存泄漏、死鎖等異常安全問題。這個(gè)需要使用RAII來處理資源的管理問題。學(xué)習(xí)成本較高。
- C++標(biāo)準(zhǔn)庫的異常體系定義得不好,導(dǎo)致大家各自定義各自的異常體系,非常的混亂。
- 異常盡量規(guī)范使用,否則后果不堪設(shè)想,隨意拋異常,外層捕獲的用戶苦不堪言。所以異常規(guī)范有兩點(diǎn):一、拋出異常類型都繼承自一個(gè)基類。二、函數(shù)是否拋異常、拋什么異常,都使用 func()throw();的方式規(guī)范化。
總結(jié)
到此這篇關(guān)于C++異常使用的文章就介紹到這了,更多相關(guān)C++異常內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解設(shè)計(jì)模式中的模板方法模式及在C++中的使用
這篇文章主要介紹了設(shè)計(jì)模式中的模板方法模式及在C++中的使用,模板方法將邏輯封裝到一個(gè)類中,并采取組合(委托)的方式解決這個(gè)問題,需要的朋友可以參考下2016-03-03詳解c語言中的 strcpy和strncpy字符串函數(shù)使用
strcpy 和strcnpy函數(shù)是字符串復(fù)制函數(shù)。接下來通過本文給大家介紹c語言中的strcpy和strncpy字符串函數(shù)使用,感興趣的朋友跟隨小編要求看看吧2018-10-10VC下通過系統(tǒng)快照實(shí)現(xiàn)進(jìn)程管理的方法
這篇文章主要介紹了VC下通過系統(tǒng)快照實(shí)現(xiàn)進(jìn)程管理的方法,較為詳細(xì)的講述了VC下通過系統(tǒng)快照實(shí)現(xiàn)進(jìn)程管理的原理與具體實(shí)現(xiàn)方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2014-10-10Windows環(huán)境給FFmpeg集成AVS3解碼器
libuavs3d是AVS3標(biāo)準(zhǔn)的解碼器,支持windows/linux/arm/ios等所有常用平臺(tái),在移動(dòng)端最高支持4K/30fps視頻實(shí)時(shí)解碼,解碼速度大幅領(lǐng)先AV1開源解碼器dav1d和aomdec,由于FFmpeg默認(rèn)未啟用libuavs3d,因此需要重新配置FFmpeg,標(biāo)明啟用libuavs3d,然后重新編譯安裝FFmpeg2024-05-05c++之std::get_time和std::put_time
std::get_time和std::put_time是C++中用于日期和時(shí)間的格式化和解析的函數(shù),它們都包含在<iomanip>頭文件中,std::get_time用于從輸入流中解析日期時(shí)間字符串,而std::put_time則用于將std::tm結(jié)構(gòu)格式化為字符串2024-10-10