亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C++ > 正文

C++中的異常處理機制詳解

2020-05-23 13:47:30
字體:
來源:轉載
供稿:網友

異常處理

增強錯誤恢復能力是提高代碼健壯性的最有力的途徑之一,C語言中采用的錯誤處理方法被認為是緊耦合的,函數的使用者必須在非常靠近函數調用的地方編寫錯誤處理代碼,這樣會使得其變得笨拙和難以使用。C++中引入了異常處理機制,這是C++的主要特征之一,是考慮問題和處理錯誤的一種更好的方式。使用錯誤處理可以帶來一些優點,如下:

錯誤處理代碼的編寫不再冗長乏味,并且不再和正常的代碼混合在一起,程序員只需要編寫希望產生的代碼,然后在后面某個單獨的區段里編寫處理錯誤的嗲嗎。多次調用同一個函數,則只需要某個地方編寫一次錯誤處理代碼。

錯誤不能被忽略,如果一個函數必須向調用者發送一次錯誤信息。它將拋出一個描述這個錯誤的對象。

傳統的錯誤處理和異常處理

在討論異常處理之前,我們先談談C語言中的傳統錯誤處理方法,這里列舉了如下三種:

在函數中返回錯誤,函數會設置一個全局的錯誤狀態標志。

使用信號來做信號處理系統,在函數中raise信號,通過signal來設置信號處理函數,這種方式耦合度非常高,而且不同的庫產生的信號值可能會發生沖突

使用標準C庫中的非局部跳轉函數 setjmp和longjmp ,這里使用setjmp和longjmp來演示下如何進行錯誤處理:

#include #include jmp_buf static_buf; //用來存放處理器上下文,用于跳轉void do_jmp() { //do something,simetime occurs a little error //調用longjmp后,會載入static_buf的處理器信息,然后第二個參數作為返回點的setjmp這個函數的返回值 longjmp(static_buf,10);//10是錯誤碼,根據這個錯誤碼來進行相應的處理 }int main() { int ret = 0; //將處理器信息保存到static_buf中,并返回0,相當于在這里做了一個標記,后面可以跳轉過來 if((ret = setjmp(static_buf)) == 0) { //要執行的代碼 do_jmp(); } else { //出現了錯誤 if (ret == 10) std::cout << "a little error" << std::endl; } }

錯誤處理方式看起來耦合度不是很高,正常代碼和錯誤處理的代碼分離了,處理處理的代碼都匯聚在一起了。但是基于這種局部跳轉的方式來處理代碼,在C++中卻存在很嚴重的問題,那就是對象不能被析構,局部跳轉后不會主動去調用已經實例化對象的析構函數。這將導致內存泄露的問題。下面這個例子充分顯示了這點

#include #include using namespace std;class base { public: base() { cout << "base construct func call" << endl; } ~base() { cout << "~base destruct func call" << endl; } };jmp_buf static_buf;void test_base() { base b; //do something longjmp(static_buf,47);//進行了跳轉,跳轉后會發現b無法析構了 }int main() { if(setjmp(static_buf) == 0) { cout << "deal with some thing" << endl; test_base(); } else { cout << "catch a error" << endl; } }

在上面這段代碼中,只有base類的構造函數會被調用,當longjmp發生了跳轉后,b這個實例將不會被析構掉,但是執行流已經無法回到這里,b這個實例將不會被析構。這就是局部跳轉用在C++中來處理錯誤的時候帶來的一些問題,在C++中異常則不會有這些問題的存在。那么接下來看看如何定義一個異常,以及如何拋出一個異常和捕獲異常吧.

異常的拋出

class MyError { const char* const data;public: MyError(const char* const msg = 0):data(msg) { //idle }};void do_error() { throw MyError("something bad happend"); }int main() { do_error(); }

上面的例子中,通過throw拋出了一個異常類的實例,這個異常類,可以是任何一個自定義的類,通過實例化傳入的參數可以表明發生的錯誤信息。其實異常就是一個帶有異常信息的類而已。異常被拋出后,需要被捕獲,從而可以從錯誤中進行恢復,那么接下來看看如何去捕獲一個異常吧。在上面這個例子中使用拋出異常的方式來進行錯誤處理相比與之前使用局部跳轉的實現來說,最大的不同之處就是異常拋出的代碼塊中,對象會被析構,稱之為堆棧反解.

異常的捕獲

C++中通過catch關鍵字來捕獲異常,捕獲異常后可以對異常進行處理,這個處理的語句塊稱為異常處理器。下面是一個簡單的捕獲異常的例子:

try{    //do something    throw string("this is exception");  } catch(const string& e) {    cout << "catch a exception " << e << endl;  }

catch有點像函數,可以有一個參數,throw拋出的異常對象,將會作為參數傳遞給匹配到到catch,然后進入異常處理器,上面的代碼僅僅是展示了拋出一種異常的情況,加入try語句塊中有可能會拋出多種異常的,那么該如何處理呢,這里是可以接多個catch語句塊的,這將導致引入另外一個問題,那就是如何進行匹配。

異常的匹配

異常的匹配我認為是符合函數參數匹配的原則的,但是又有些不同,函數匹配的時候存在類型轉換,但是異常則不然,在匹配過程中不會做類型的轉換,下面的例子說明了這個事實:

#include using namespace std; int main() { try{ throw 'a'; }catch(int a) { cout << "int" << endl; }catch(char c) { cout << "char" << endl; } }

上面的代碼的輸出結果是char,因為拋出的異常類型就是char,所以就匹配到了第二個異常處理器??梢园l現在匹配過程中沒有發生類型的轉換。將char轉換為int。盡管異常處理器不做類型轉換,但是基類可以匹配到派生類這個在函數和異常匹配中都是有效的,但是需要注意catch的形參需要是引用類型或者是指針類型,否則會 導致切割派生類這個問題。

//基類class Base{ public: Base(string msg):m_msg(msg) { } virtual void what(){ cout << m_msg << endl; } void test() { cout << "I am a CBase" << endl; } protected: string m_msg;};//派生類,重新實現了虛函數class CBase : public Base{ public: CBase(string msg):Base(msg) { } void what() { cout << "CBase:" << m_msg << endl; } };int main() { try { //do some thing //拋出派生類對象 throw CBase("I am a CBase exception"); }catch(Base& e) { //使用基類可以接收 e.what(); } }

上面的這段代碼可以正常的工作,實際上我們日常編寫自己的異常處理函數的時候也是通過繼承標準異常來實現字節的自定義異常的,但是如果將Base&換成Base的話,將會導致對象被切割,例如下面這段代碼將會編譯出錯,因為CBase被切割了,導致CBase中的test函數無法被調用。

try {    //do some thing    throw CBase("I am a CBase exception");}catch(Base e) {e.test();}

到此為此,異常的匹配算是說清楚了,總結一下,異常匹配的時候基本上遵循下面幾條規則:

異常匹配除了必須要是嚴格的類型匹配外,還支持下面幾個類型轉換.

允許非常量到常量的類型轉換,也就是說可以拋出一個非常量類型,然后使用catch捕捉對應的常量類型版本

允許從派生類到基類的類型轉換

允許數組被轉換為數組指針,允許函數被轉換為函數指針

假想一種情況,當我要實現一代代碼的時候,希望無論拋出什么類型的異常我都可以捕捉到,目前來說我們只能寫上一大堆的catch語句捕獲所有可能在代碼中出現的異常來解決這個問題,很顯然這樣處理起來太過繁瑣,幸好C++提供了一種可以捕捉任何異常的機制,可以使用下列代碼中的語法。

catch(...) {
    //異常處理器,這里可以捕捉任何異常,帶來的問題就是無法或者異常信息
   }
如果你要實現一個函數庫,你捕捉了你的函數庫中的一些異常,但是你只是記錄日志,并不去處理這些異常,處理異常的事情會交給上層調用的代碼來處理.對于這樣的一個場景C++也提供了支持.

try{    throw Exception("I am a exception");    }catch(...) {    //log the exception    throw;  }

通過在catch語句塊中加入一個throw,就可以把當前捕獲到的異常重新拋出.在異常拋出的那一節中,我在代碼中拋出了一個異常,但是我沒有使用任何catch語句來捕獲我拋出的這個異常,執行上面的程序會出現下面的結果.

terminate called after throwing an instance of 'MyError'
Aborted (core dumped)

為什么會出現這樣的結果呢?,當我們拋出一個異常的時候,異常會隨著函數調用關系,一級一級向上拋出,直到被捕獲才會停止,如果最終沒有被捕獲將會導致調用terminate函數,上面的輸出就是自動調用terminate函數導致的,為了保證更大的靈活性,C++提供了set_terminate函數可以用來設置自己的terminate函數.設置完成后,拋出的異常如果沒有被捕獲就會被自定義的terminate函數進行處理.下面是一個使用的例子:

#include #include #include using namespace std;class MyError { const char* const data; public: MyError(const char* const msg = 0):data(msg) { //idle } };void do_error() { throw MyError("something bad happend"); } //自定義的terminate函數,函數原型需要一致 void terminator() { cout << "I'll be back" << endl; exit(0); }int main() { //設置自定義的terminate,返回的是原有的terminate函數指針 void (*old_terminate)() = set_terminate(terminator); do_error(); }

 上面的代碼會輸出I'll be back
到此為此關于異常匹配的我所知道的知識點都已經介紹完畢了,那么接著可以看看下一個話題,異常中的資源清理.

異常中的資源清理

在談到局部跳轉的時候,說到局部調轉不會調用對象的析構函數,會導致內存泄露的問題,C++中的異常則不會有這個問題,C++中通過堆棧反解將已經定義的對象進行析構,但是有一個例外就是構造函數中如果出現了異常,那么這會導致已經分配的資源無法回收,下面是一個構造函數拋出異常的例子:

#include #include using namespace std;class base { public: base() { cout << "I start to construct" << endl; if (count == 3) //構造第四個的時候拋出異常 throw string("I am a error"); count++; } ~base() { cout << "I will destruct " << endl; } private: static int count; };int base::count = 0;int main() { try{ base test[5]; } catch(...){ cout << "catch some error" << endl; } }

 上面的代碼輸出結果是:

 I start to construct I start to construct I start to construct I start to construct I will destruct  I will destruct  I will destruct  catch some error

在上面的代碼中構造函數發生了異常,導致對應的析構函數沒有執行,因此實際編程過程中應該避免在構造函數中拋出異常,如果沒有辦法避免,那么一定要在構造函數中對其進行捕獲進行處理.最后介紹一個知識點就是函數try語句塊,如果main函數可能會拋出異常該怎么捕獲?,如果構造函數中的初始化列表可能會拋出異常該怎么捕獲?下面的兩個例子說明了函數try語句塊的用法:

#include using namespace std;int main() try { throw "main"; } catch(const char* msg) { cout << msg << endl; return 1; }

 main函數語句塊,可以捕獲main函數中拋出的異常.

 class Base { public: Base(int data,string str)try:m_int(data),m_string(str)//對初始化列表中可能會出現的異常也會進行捕捉 { // some initialize opt }catch(const char* msg) { cout << "catch a exception" << msg << endl; } private: int m_int; string m_string; };int main() { Base base(1,"zhangyifei"); }

上面說了很多都是關于異常的使用,如何定義自己的異常,編寫異常是否應該遵循一定的標準,在哪里使用異常,異常是否安全等等一系列的問題,下面會一一討論的.

標準異常

C++標準庫給我們提供了一系列的標準異常,這些標準異常都是從exception類派生而來,主要分為兩大派生類,一類是logic_error,另一類則是runtime_error這兩個類在stdexcept頭文件中,前者主要是描述程序中出現的邏輯錯誤,例如傳遞了無效的參數,后者指的是那些無法預料的事件所造成的錯誤,例如硬件故障或內存耗盡等,這兩者都提供了一個參數類型為std::string的構造函數,這樣就可以將異常信息保存起來,然后通過what成員函數得到異常信息.

#include #include #include using namespace std;class MyError:public runtime_error { public: MyError(const string& msg = "") : runtime_error(msg) {}};//runtime_error logic_error 兩個都是繼承自標準異常,帶有string構造函數 // int main() { try { throw MyError("my message");  } catch(MyError& x) { cout << x.what() << endl;  } }

異常規格說明

假設一個項目中使用了一些第三方的庫,那么第三方庫中的一些函數可能會拋出異常,但是我們不清楚,那么C++提供了一個語法,將一個函數可能會拋出的異常列出來,這樣我們在編寫代碼的時候參考函數的異常說明即可,但是C++11中這中異常規格說明的方案已經被取消了,所以我不打算過多介紹,通過一個例子看看其基本用法即可,重點看看C++11中提供的異常說明方案:

#include #include #include #include using namespace std;class Up{}; class Fit{}; void g(); //異常規格說明,f函數只能拋出Up 和Fit類型的異常 void f(int i)throw(Up,Fit) { switch(i) { case 1: throw Up(); case 2: throw Fit();  } g(); }void g() {throw 47;}void my_ternminate() { cout << "I am a ternminate" << endl; exit(0); }void my_unexpected() { cout << "unexpected exception thrown" << endl; // throw Up(); throw 8; //如果在unexpected中繼續拋出異常,拋出的是規格說明中的 則會被捕捉程序繼續執行 //如果拋出的異常不在異常規格說明中分兩種情況 //1.異常規格說明中有bad_exception ,那么會導致拋出一個bad_exception //2.異常規格說明中沒有bad_exception 那么會導致程序調用ternminate函數 // exit(0); }int main() { set_terminate(my_ternminate); set_unexpected(my_unexpected); for(int i = 1;i <=3;i++) { //當拋出的異常,并不是異常規格說明中的異常時 //會導致最終調用系統的unexpected函數,通過set_unexpected可以 //用來設置自己的unexpected汗函數 try { f(i);  }catch(Up) { cout << "Up caught" << endl;  }catch(Fit) { cout << "Fit caught" << endl;  }catch(bad_exception) { cout << "bad exception" << endl;  } } } }

上面的代碼說明了異常規格說明的基本語法,以及unexpected函數的作用,以及如何自定義自己的unexpected函數,還討論了在unexpected函數中繼續拋出異常的情況下,該如何處理拋出的異常.C++11中取消了這種異常規格說明.引入了一個noexcept函數,用于表明這個函數是否會拋出異常

void recoup(int) noexecpt(true); //recoup不會拋出異常void recoup(int) noexecpt(false); //recoup可能會拋出異常

此外還提供了noexecpt用來檢測一個函數是否不拋出異常.

異常安全

異常安全我覺得是一個挺復雜的點,不光光需要實現函數的功能,還要保存函數不會在拋出異常的情況下,出現不一致的狀態.這里舉一個例子,大家在實現堆棧的時候經??吹綍械睦佣际嵌x了一個top函數用來獲得棧頂元素,還有一個返回值是void的pop函數僅僅只是把棧頂元素彈出,那么為什么沒有一個pop函數可以 即彈出棧頂元素,并且還可以獲得棧頂元素呢?

template<typename T> T stack<T>::pop(){  if(count == 0)    throw logic_error("stack underflow");  else    return data[--count];}

如果函數在最后一行拋出了一個異常,那么這導致了函數沒有將退棧的元素返回,但是Count已經減1了,所以函數希望得到的棧頂元素丟失了.本質原因是因為這個函數試圖一次做兩件事,1.返回值,2.改變堆棧的狀態.最好將這兩個獨立的動作放到兩個獨立的函數中,遵守內聚設計的原則,每一個函數只做一件事.我們 再來討論另外一個異常安全的問題,就是很常見的賦值操作符的寫法,如何保證賦值操作是異常安全的.

class Bitmap {...};class Widget {  ...private:  Bitmap *pb;};Widget& Widget::operator=(const Widget& rhs){delete pb;pb = new Bitmap(*rhs.pb);return *this;}

上面的代碼不具備自我賦值安全性,倘若rhs就是對象本身,那么將會導致*rhs.pb指向一個被刪除了的對象.那么就緒改進下.加入證同性測試.

Widget& Widget::operator=(const Widget& rhs){  If(this == rhs) return *this; //證同性測試  delete pb;  pb = new Bitmap(*rhs.pb);  return *this;}

但是現在上面的代碼依舊不符合異常安全性,因為如果delete pb執行完成后在執行new Bitmap的時候出現了異常,則會導致最終指向一塊被刪除的內存.現在只要稍微改變一下,就可以讓上面的代碼具備異常安全性.

Widget& Widget::operator=(const Widget& rhs){  If(this == rhs) return *this; //證同性測試  Bitmap *pOrig = pb;  pb = new Bitmap(*rhs.pb); //現在這里即使發生了異常,也不會影響this指向的對象  delete pOrig;  return *this;  }

這個例子看起來還是比較簡單的,但是用處還是很大的,對于賦值操作符來說,很多情況都是需要重載的.


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲欧美国产精品专区久久| 国产精品欧美激情在线播放| 91欧美精品成人综合在线观看| 欧美日韩裸体免费视频| 久久久中文字幕| 亚洲精品国精品久久99热| 成人在线视频网| 久久人91精品久久久久久不卡| 91在线观看免费高清完整版在线观看| 国产盗摄xxxx视频xxx69| 国产专区欧美专区| 欧美中文字幕在线观看| www.亚洲免费视频| 日韩电视剧免费观看网站| 亚洲黄在线观看| 亚洲一区二区三区xxx视频| 欧美视频免费在线| 国产精品夜间视频香蕉| 欧美电影免费在线观看| 97在线免费观看视频| 国外成人在线直播| 亚洲午夜久久久久久久| 久久福利视频导航| 亚洲欧美日韩天堂一区二区| 日韩av在线网页| 久久精品国产综合| 91在线免费观看网站| 欧美高清自拍一区| 欧美日韩国产一区二区三区| 亚洲男子天堂网| 欧美中在线观看| 欧美xxxwww| 午夜精品在线观看| 亚洲а∨天堂久久精品喷水| 欧美性极品xxxx做受| 亚洲大胆人体在线| 日韩在线免费av| 色视频www在线播放国产成人| 欧美性理论片在线观看片免费| 这里只有精品在线播放| 久久久久久久一区二区| 亚洲嫩模很污视频| 在线观看日韩欧美| 国产午夜精品麻豆| 精品视频久久久久久久| 欧美—级高清免费播放| 国产91精品在线播放| 亚洲人成电影在线播放| 久久精品美女视频网站| 亚洲国产又黄又爽女人高潮的| 中国china体内裑精亚洲片| 久久人人爽人人爽爽久久| 日韩成人久久久| 精品国产91久久久久久| 日本中文字幕久久看| 亚洲精品欧美极品| 中文字幕亚洲综合久久筱田步美| 91高清在线免费观看| 孩xxxx性bbbb欧美| 日韩成人在线电影网| 欧美日韩国产精品一区二区三区四区| 国产啪精品视频网站| 精品在线欧美视频| 97超碰蝌蚪网人人做人人爽| 欧美亚洲免费电影| 成人精品网站在线观看| 奇米成人av国产一区二区三区| 久久69精品久久久久久久电影好| 亚洲自拍小视频免费观看| 欧美亚洲国产日韩2020| 亚洲韩国欧洲国产日产av| 95av在线视频| 亚洲欧美日韩精品久久奇米色影视| 日韩一中文字幕| 欧美午夜无遮挡| 欧美激情亚洲激情| 亚洲精品久久久久中文字幕欢迎你| 国产精品小说在线| 日韩激情在线视频| 九九精品在线观看| 国产精欧美一区二区三区| 亚洲自拍偷拍视频| 成人97在线观看视频| 亚洲午夜激情免费视频| 久久国产色av| 亚洲福利视频久久| 欧美成人激情在线| 亚洲欧美精品伊人久久| 久久久人成影片一区二区三区观看| 91精品国产777在线观看| 欧美怡红院视频一区二区三区| 日韩在线观看免费高清完整版| 成人午夜在线视频一区| 久久精品亚洲国产| 亚洲国产精品va在看黑人| 亚洲r级在线观看| 亚洲第一视频网| 欧美日韩激情视频8区| 国产精品香蕉在线观看| 91超碰中文字幕久久精品| 国内精品国产三级国产在线专| 久久精品国产精品| 黄色成人av在线| 日韩大片免费观看视频播放| 亚洲欧美色婷婷| 欧美成人中文字幕在线| 性欧美xxxx视频在线观看| 成人黄色免费网站在线观看| 日韩电视剧在线观看免费网站| 久久精品视频在线观看| 在线播放国产一区二区三区| 午夜精品一区二区三区在线视频| 欧美另类极品videosbest最新版本| 精品国产一区二区三区久久久狼| 成人激情av在线| 91大神福利视频在线| 日韩在线观看免费全| 亚洲图片在区色| 性视频1819p久久| 中文字幕欧美日韩va免费视频| 欧美黑人xxxⅹ高潮交| 亚洲国产精品电影在线观看| 精品毛片网大全| 日韩精品极品视频免费观看| 伊人久久综合97精品| 色综合视频一区中文字幕| 亚洲欧洲偷拍精品| 日韩精品中文字幕在线观看| 欧美性猛交视频| 欧美精品videosex性欧美| 亚洲字幕在线观看| 亚洲精品99久久久久中文字幕| 亚洲精品一区久久久久久| 91久久久久久久久久久| 国产伊人精品在线| 欧美最猛黑人xxxx黑人猛叫黄| 亚洲亚裔videos黑人hd| 欧美在线视频免费| 欧美日韩免费看| 亚洲国产精品人久久电影| 国产欧美在线看| 亚洲黄色在线观看| 蜜月aⅴ免费一区二区三区| 亚洲人成电影网站色xx| 国产在线观看一区二区三区| 91国内免费在线视频| 国自产精品手机在线观看视频| 久久av中文字幕| 97国产成人精品视频| 日韩精品亚洲元码| 九九热精品视频国产| 精品国产91久久久久久老师| 国产精品久久二区| 国内偷自视频区视频综合| 欧洲亚洲在线视频| 欧美老女人在线视频| 国产精品男女猛烈高潮激情| 国产精品视频久久久久| 中文字幕国内精品| 66m—66摸成人免费视频| 九九热视频这里只有精品| 成人亚洲欧美一区二区三区| 精品日本美女福利在线观看| 欧美黄网免费在线观看|