C++| |异常
異常
# 前言
異常是一種處理錯(cuò)誤的方式。當(dāng)一個(gè)函數(shù)發(fā)現(xiàn)自己無法處理的錯(cuò)誤時(shí)就可以拋出異常,讓函數(shù)的直接或者間接調(diào)用者處理這個(gè)函數(shù)
-  throw:當(dāng)問題出現(xiàn)時(shí),程序會(huì)拋出一個(gè)異常。通過throw關(guān)鍵字來實(shí)現(xiàn) 
-  catch:在想要處理問題的地方,通過異常處理程序捕獲異常,可以有多個(gè)catch捕獲 
-  try:try塊中的代碼標(biāo)識(shí)將被激活特定的異常。后面通常跟這一個(gè)或者多個(gè)catch塊 
如果有一個(gè)塊中拋出異常的話,捕獲異常的方法是try和catch關(guān)鍵字。try塊中放置可能拋出異常的代碼。try塊中的代碼稱為保護(hù)代碼。
?
# 異常的使用
# 異常的拋出和捕獲
異常的拋出和匹配的原則
-  異常是通過拋出對(duì)象而引發(fā)的,該對(duì)象的類型決定要引發(fā)哪一個(gè)catch的處理代碼 
-  被選中的處理代碼是調(diào)用鏈中和該對(duì)象類型匹配并且距離拋出異常位置最近的哪一個(gè) 
-  拋出異常對(duì)象后,會(huì)生成一個(gè)異常對(duì)象的拷貝,因?yàn)?strong>拋出的異常對(duì)象有可能是一個(gè)臨時(shí)對(duì)象。所以會(huì)生成一個(gè)臨時(shí)對(duì)象,這個(gè)拷貝的臨時(shí)對(duì)象會(huì)在被catch之后銷毀 
-  catch(...)可以捕獲各種類型的異常 
-  實(shí)際中拋出和匹配的原則有著一個(gè)例外。可以拋出派生類對(duì)象,使用基類捕獲(實(shí)際中經(jīng)常使用) 
在函數(shù)調(diào)用棧中異常棧展開的原則
首先檢查throw本身是否在try的內(nèi)部,如果是的話就查找匹配的catch語句,如果有匹配的就調(diào)用調(diào)用catch的處理代碼進(jìn)行處理
沒有的話,那就退出當(dāng)前的函數(shù)棧,繼續(xù)在調(diào)用函數(shù)的棧中進(jìn)行查找匹配的catch
如果到達(dá)main函數(shù)的棧,還沒有找到匹配的catch,那就終止程序
找到匹配的catch子句并處理之后,需繼續(xù)沿著catch子句后面繼續(xù)執(zhí)行
【注意】:
所以在實(shí)際情況當(dāng)中我們都要加上一個(gè)catch(...)捕獲任意類型的異常,否則當(dāng)有異常沒有被捕獲到的時(shí)候,程序就會(huì)終止
?
# 異常的重新拋出
有可能單個(gè)的catch不能完全處理一個(gè)異常,在進(jìn)行一些校正處理之后,希望再次交給更外層的調(diào)用鏈函數(shù)來處理,catch則可以重新拋出異常交給上層的函數(shù)進(jìn)行處理
例:
#include <iostream> #include <vector> ? using namespace std; ? double Division(int x, int y) {if (y == 0){throw "Divison by zero condition";} ?return (double)x / (double)y; } ? void Func() {int* array = new int[10];try {int x, y;std::cin >> x >> y;std::cout << Division(x, y) << std::endl;}catch(...){cout << "delete[]" << array << std::endl; delete[] array;throw;}std::cout << "delete[]" << array << std::endl;delete[] array; } ? int main() {try {Func();}catch(const char* errmsg){std::cout << errmsg <<std::endl;}catch(...){std::cout << "unkown exception!" << std::endl;}return ?0; }?
# 異常安全
-  構(gòu)造函數(shù)通常完成對(duì)象的初始化,不要再構(gòu)造函數(shù)中進(jìn)行拋異常,有可能導(dǎo)致對(duì)象沒有初始化完全 
-  析構(gòu)函數(shù)通常完成資源的清理,不要再析構(gòu)函數(shù)中進(jìn)行拋異常,有可能導(dǎo)致資源泄露 
-  C++異常會(huì)導(dǎo)致執(zhí)行流的亂跳轉(zhuǎn),所以就會(huì)導(dǎo)致資源泄露的問題。比如,在new和delete中拋出異常,導(dǎo)致內(nèi)存泄露,在lock和unlock中拋出異常導(dǎo)致死鎖的問題。 
?
# 異常的規(guī)范
異常規(guī)格說明的目的是為了讓函數(shù)使用者直到該函數(shù)可以拋出的異常有哪些
在函數(shù)的后面接throw(類型, ...),列出這個(gè)函數(shù)可能拋出的所有異常的類型
函數(shù)的后面接上throw(),表示這個(gè)函數(shù)不會(huì)拋出異常
若無異常接口聲明表示這個(gè)函數(shù)可能拋出任何類型的異常
例:
void Func() throw(const char*) //表明這個(gè)函數(shù)可能拋出const char*類型的異常void Func() throw() //表明這個(gè)函數(shù)不會(huì)拋出異常void Func() //表明這個(gè)函數(shù)可能拋出任何類型的異常?
# 異常的標(biāo)準(zhǔn)體系
-  自己定義的異常標(biāo)準(zhǔn)體系 -  對(duì)于一個(gè)公司來說的話一般都是自己定義一個(gè)異常標(biāo)準(zhǔn)體系。這樣的話大家拋出的都是派生類對(duì)象,然后捕獲一個(gè)基類就行了 
 
-  
-  C++標(biāo)準(zhǔn)庫異常體系 -  以父子類層次結(jié)構(gòu)組織起來的 
 
-  
他們都是采用繼承的方式實(shí)現(xiàn)異常體系的,并且拋出對(duì)象的時(shí)候一般都是派生類對(duì)象采用基類對(duì)象來進(jìn)行捕獲異常
?
# 異常的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
異常對(duì)象定義好了,相比于錯(cuò)誤碼可以清晰地展示出各種的錯(cuò)誤信息,可以更好的定位程序的bug
對(duì)于錯(cuò)誤碼的方式有著一個(gè)極大的缺點(diǎn),那就是在函數(shù)調(diào)用鏈中,深層次的函數(shù)返回了錯(cuò)誤,必須要層層返回錯(cuò)誤,最外層才能拿到錯(cuò)誤。異常可以進(jìn)行執(zhí)行流的跳轉(zhuǎn)
很多的第三方庫都包含異常。比如boost,gtest,gmock庫
很多測試框架都使用異常這樣才可以進(jìn)行白盒測試
部分函數(shù)使用異常可以更好的處理,比如構(gòu)造函數(shù)沒有返回
缺點(diǎn):
異常會(huì)導(dǎo)致執(zhí)行流的亂跳,并且運(yùn)行時(shí)出錯(cuò)異常就會(huì)亂流
C++沒有垃圾回收機(jī)制,資源需要自己進(jìn)行管理,有了一場非常容易導(dǎo)致內(nèi)存泄露,死鎖等異常安全問題
C++標(biāo)準(zhǔn)庫異常體系定義的不好,導(dǎo)致大家各自定義各自的異常體系,非常的混亂
異常盡量規(guī)范使用,否則后果不堪設(shè)想,隨意拋異常,外層捕獲的用戶苦不堪言。
異常規(guī)范
-  拋出異常類型都繼承自一個(gè)基類 
-  函數(shù)是否拋出異常,拋出什么異常都采用Func() throw()的方式規(guī)范化 
?
# 總結(jié)
異常總體來說,利大于弊,所以對(duì)于一個(gè)工程來說還是多用異常。并且OO(面向?qū)ο?#xff09;的語言都是使用異常來處理錯(cuò)誤。
大家可以看一下我寫的代碼:
https://github.com/YKitty/LinuxDir/tree/master/C%2B%2BCode/abnormal?
總結(jié)
 
                            
                        - 上一篇: 简单几个步骤,通过github搭建浪漫的
- 下一篇: 实战: 对GBDT(lightGBM)分
