當(dāng)前位置:首頁 > 公眾號(hào)精選 > 嵌入式微處理器
[導(dǎo)讀]關(guān)于c++的異常處理,網(wǎng)上有很多的爭議,本文會(huì)介紹c++的異常處理的使用,以及我們應(yīng)該使用異常處理嗎,以及使用異常處理需要注意的地方。 什么是異常處理? 異常處理當(dāng)然指的是對異常的處理,異常是指程序在執(zhí)行期間產(chǎn)生的問題,沒有按正確設(shè)想的流程走下去,

關(guān)于c++的異常處理,網(wǎng)上有很多的爭議,本文會(huì)介紹c++的異常處理的使用,以及我們應(yīng)該使用異常處理嗎,以及使用異常處理需要注意的地方。

什么是異常處理?

異常處理當(dāng)然指的是對異常的處理,異常是指程序在執(zhí)行期間產(chǎn)生的問題,沒有按正確設(shè)想的流程走下去,比如除以零的操作,異常處理提供了一種轉(zhuǎn)移程序控制權(quán)的方式,這里涉及到三個(gè)關(guān)鍵字:

  • throw:當(dāng)問題出現(xiàn)時(shí),程序會(huì)通過throw來拋出一個(gè)異常

  • catch:在可能有throw想要處理問題的地方,通過catch關(guān)鍵字來捕獲異常

  • try:try塊中的代碼標(biāo)識(shí)將被激活的特定異常,它后面通常跟著一個(gè)或多個(gè)catch塊

直接看示例代碼:

void func() { throw exception; // 拋出異常}
int main() { try { // try里放置可能拋出異常的代碼,塊中的代碼被稱為保護(hù)代碼 func(); } catch (exception1& e) { // 捕獲異常,異常類型為exception1 // code } catch (exception2& e) { // 捕獲異常,異常類型為exception2 // code } catch (...) { // code } return 0;}

c++標(biāo)準(zhǔn)都有什么異常?

C++ 提供了一系列標(biāo)準(zhǔn)的異常,定義在<exception> 中,我們可以在程序中使用這些標(biāo)準(zhǔn)的異常。它們是以父子類層次結(jié)構(gòu)組織起來的,如下所示:

圖片來自菜鳥教程

具體異常應(yīng)該不需要特別介紹了吧,看英文名字就可以知道大概意思。

自定義異常

可以通過繼承和重載exception類來自定義異常,見代碼:

#include <stdexcept>class MyException : public std::runtime_error {public: MyException() : std::runtime_error("MyException") { }};void f(){ // ... throw MyException();}
int main() { try { f(); } catch (MyException& e) { // ... } catch (...) { } return 0;}

我們應(yīng)該使用異常嗎?

在c++中關(guān)于是否使用異常一直都有爭議,典型的就是知乎上陳碩大神說的不應(yīng)該使用異常,還有就是google和美國國防部等都明確定義編碼規(guī)范來禁止在c++中使用異常,這里我找了很多中英文資料,在文末參考鏈接列舉了一些。

關(guān)于是否使用異常的討論帖子在這,https://www.zhihu.com/question/22889420

陳碩大神說的什么我就不貼出來了,他水平之高無需置疑,但他說的一些東西還是很有爭議的,關(guān)于異常處理,引用吳詠煒老師的一句話:“陳碩當(dāng)然是個(gè)技術(shù)大牛。不過,在編程語言這件事上,我更愿意信任 Bjarne Stroustrup、Herb Sutter、Scott Meyers 和 Andrei Alexandrescu。這些大神們都認(rèn)為異常是比返回錯(cuò)誤碼更好的錯(cuò)誤處理方式。”

而google明確禁用異常其實(shí)是有歷史包袱的,他們也認(rèn)同異常處理是比錯(cuò)誤碼更好的處理方式,但他們別無選擇,因?yàn)橐郧暗木幾g器對異常處理的不好,他們項(xiàng)目里面已經(jīng)有了大量的非異常安全的代碼,如果全改成異常處理的代碼是有很大的工作量的,具體可以看上面的鏈接和我文末引用的一些鏈接。

美國國防部禁用異常是出于實(shí)時(shí)性能考慮,工具鏈不能保證程序拋出異常時(shí)的實(shí)時(shí)性能,但國防部禁用了很多c++特性,例如內(nèi)存分配,我們真的追求飛機(jī)一樣的高性能嗎?

通過上面的介紹大家應(yīng)該能猜到我的結(jié)論了吧,當(dāng)然這不是我的結(jié)論,而是大佬們的結(jié)論:推薦使用異常處理

異常處理有一些潛在的缺點(diǎn):

  • 會(huì)有限的影響程序的性能,但正常工作流中不拋出異常的時(shí)候速度和普通函數(shù)一樣快,甚至更快

  • 會(huì)導(dǎo)致程序體積變大10%-20%,但我們真的那么在乎程序的體積嗎(除了移動(dòng)端)

異常處理相對于使用錯(cuò)誤碼的好處:

  • 如果不使用trycatch那就需要使用返回錯(cuò)誤碼的方式,那就必然增加ifelse語句,每次函數(shù)返回后都會(huì)增加判斷的開銷,如果可以消除trycatch,代碼可能會(huì)更健壯,舉例如下:

void f1(){ try { // ... f2(); // ...} catch (some_exception& e) { // ...code that handles the error...}}void f2() { ...; f3(); ...; }void f3() { ...; f4(); ...; }void f4() { ...; f5(); ...; }void f5() { ...; f6(); ...; }void f6() { ...; f7(); ...; }void f7() { ...; f8(); ...; }void f8() { ...; f9(); ...; }void f9() { ...; f10(); ...; }void f10(){ // ... if ( /*...some error condition...*/ ) throw some_exception(); // ...}

而使用錯(cuò)誤碼方式:

int f1(){ // ... int rc = f2(); if (rc == 0) { // ...} else { // ...code that handles the error...}}int f2(){ // ... int rc = f3(); if (rc != 0) return rc; // ... return 0;}int f3(){ // ... int rc = f4(); if (rc != 0) return rc; // ... return 0;}int f4(){ // ... int rc = f5(); if (rc != 0) return rc; // ... return 0;}int f5(){ // ... int rc = f6(); if (rc != 0) return rc; // ... return 0;}int f6(){ // ... int rc = f7(); if (rc != 0) return rc; // ... return 0;}int f7(){ // ... int rc = f8(); if (rc != 0) return rc; // ... return 0;}int f8(){ // ... int rc = f9(); if (rc != 0) return rc; // ... return 0;}int f9(){ // ... int rc = f10(); if (rc != 0) return rc; // ... return 0;}int f10(){ // ... if (...some error condition...) return some_nonzero_error_code; // ... return 0;}

錯(cuò)誤碼方式對于問題的反向傳遞很麻煩,導(dǎo)致代碼腫脹,假如中間有一個(gè)環(huán)節(jié)忘記處理或處理有誤就會(huì)導(dǎo)致bug的產(chǎn)生,異常處理對于錯(cuò)誤的處理更簡潔,可以更方便的把錯(cuò)誤信息反饋給調(diào)用者,同時(shí)不需要調(diào)用者使用額外的ifelse分支來處理成功或者不成功的情況。

  • 一般來說使用錯(cuò)誤碼方式標(biāo)明函數(shù)是否成功執(zhí)行,一個(gè)值標(biāo)明函數(shù)成功執(zhí)行,另外一個(gè)或者多個(gè)值標(biāo)明函數(shù)執(zhí)行失敗,不同的錯(cuò)誤碼標(biāo)明不同的錯(cuò)誤類型,調(diào)用者需要對不同的錯(cuò)誤類型使用多個(gè)ifelse分支來處理。如果有更多ifelse,那么必然寫出更多測試用例,必然花費(fèi)更多精力,導(dǎo)致項(xiàng)目晚上線。

拿數(shù)值運(yùn)算代碼舉例:

class Number {public: friend Number operator+ (const Number& x, const Number& y); friend Number operator- (const Number& x, const Number& y); friend Number operator* (const Number& x, const Number& y); friend Number operator/ (const Number& x, const Number& y); // ...};

最簡單的可以這樣調(diào)用:

void f(Number x, Number y) { // ... Number sum = x + y; Number diff = x - y; Number prod = x * y; Number quot = x / y; // ...}

但是如果需要處理錯(cuò)誤,例如除0或者數(shù)值溢出等,函數(shù)得到的就是錯(cuò)誤的結(jié)果,調(diào)用者需要做處理。

先看使用錯(cuò)誤碼的方式:

class Number {public: enum ReturnCode { Success, Overflow, Underflow, DivideByZero}; Number add(const Number& y, ReturnCode& rc) const; Number sub(const Number& y, ReturnCode& rc) const; Number mul(const Number& y, ReturnCode& rc) const; Number div(const Number& y, ReturnCode& rc) const; // ...};
int f(Number x, Number y){ // ... Number::ReturnCode rc; Number sum = x.add(y, rc); if (rc == Number::Overflow) { // ...code that handles overflow... return -1;} else if (rc == Number::Underflow) { // ...code that handles underflow... return -1;} else if (rc == Number::DivideByZero) { // ...code that handles divide-by-zero... return -1;} Number diff = x.sub(y, rc); if (rc == Number::Overflow) { // ...code that handles overflow... return -1;} else if (rc == Number::Underflow) { // ...code that handles underflow... return -1;} else if (rc == Number::DivideByZero) { // ...code that handles divide-by-zero... return -1;} Number prod = x.mul(y, rc); if (rc == Number::Overflow) { // ...code that handles overflow... return -1;} else if (rc == Number::Underflow) { // ...code that handles underflow... return -1;} else if (rc == Number::DivideByZero) { // ...code that handles divide-by-zero... return -1;} Number quot = x.div(y, rc); if (rc == Number::Overflow) { // ...code that handles overflow... return -1;} else if (rc == Number::Underflow) { // ...code that handles underflow... return -1;} else if (rc == Number::DivideByZero) { // ...code that handles divide-by-zero... return -1;} // ...}

再看使用異常處理的方式:

void f(Number x, Number y){ try { // ... Number sum = x + y; Number diff = x - y; Number prod = x * y; Number quot = x / y; // ...} catch (Number::Overflow& exception) { // ...code that handles overflow...} catch (Number::Underflow& exception) { // ...code that handles underflow...} catch (Number::DivideByZero& exception) { // ...code that handles divide-by-zero...}}

如果有更多的運(yùn)算,或者有更多的錯(cuò)誤碼,異常處理的優(yōu)勢會(huì)更明顯。

  • 使用異??梢允沟么a邏輯更清晰,將代碼按正確的邏輯列出來,邏輯更緊密代碼更容易讀懂,而錯(cuò)誤處理可以單獨(dú)放到最后做處理。

  • 異??梢赃x擇自己處理或者傳遞給上層處理

異常處理的關(guān)鍵點(diǎn)

  1. 不應(yīng)該使用異常處理做什么?

  • throw僅用于拋出一個(gè)錯(cuò)誤,標(biāo)識(shí)函數(shù)沒有按設(shè)想的方式去執(zhí)行

  • 只有在知道可以處理錯(cuò)誤時(shí),才使用catch來捕獲錯(cuò)誤,例如轉(zhuǎn)換類型或者內(nèi)存分配失敗

  • 不要使用throw來拋出編碼錯(cuò)誤,應(yīng)該使用assert或者其它方法告訴編譯器或者崩潰進(jìn)程收集debug信息

  • 如果有必須要崩潰的事件,或者無法恢復(fù)的問題,不應(yīng)該使用throw拋出,因?yàn)閽伋鰜硗獠恳矡o法處理,就應(yīng)該讓程序崩潰

  • try、catch不應(yīng)該簡單的用于函數(shù)返回值,函數(shù)的返回值應(yīng)該使用return操作,不應(yīng)該使用catch,這會(huì)給編程人員帶來誤解,同時(shí)也不應(yīng)該用異常來跳出循環(huán)

異常處理看似簡單好用,但它需要項(xiàng)目成員嚴(yán)格遵守開發(fā)規(guī)范,定好什么時(shí)候使用異常,什么時(shí)候不使用,而不是既使用異常又使用錯(cuò)誤碼方式。

  • 構(gòu)造函數(shù)可以拋出異常嗎?可以而且建議使用異常,因?yàn)闃?gòu)造函數(shù)沒有返回值,所以只能拋出異常,也有另一種辦法就是添加一個(gè)成員變量標(biāo)識(shí)對象是否構(gòu)造成功,這種方法那就會(huì)額外添加一個(gè)返回該返回值的函數(shù),如果定義一個(gè)對象數(shù)組那就需要對數(shù)組每個(gè)對象都判斷是否構(gòu)造成功,這種代碼不太好。

  • 構(gòu)造函數(shù)拋出異常會(huì)產(chǎn)生內(nèi)存泄漏嗎?不會(huì),構(gòu)造函數(shù)拋出異常產(chǎn)生內(nèi)存泄漏那是編譯器的bug,已經(jīng)在21世紀(jì)修復(fù),不要聽信謠言。

    void f() { X x; // If X::X() throws, the memory for x itself will not leak Y* p = new Y(); // If Y::Y() throws, the memory for *p itself will not leak}
  • 永遠(yuǎn)不要在析構(gòu)函數(shù)中把異常拋出,還是拿對象數(shù)組舉例,數(shù)組里有多個(gè)對象,如果其中一個(gè)對象析構(gòu)過程中拋出異常,會(huì)導(dǎo)致剩余的對象都無法被析構(gòu),析構(gòu)函數(shù)應(yīng)該捕獲異常并把他們吞下或者終止程序,而不是拋出。

  • 構(gòu)造函數(shù)內(nèi)申請完資源后拋出異常怎么辦?使用智能指針,關(guān)于char*也可以使用std::string代替。


    #include <memory>
    using namespace std;
    class SPResourceClass {private: shared_ptr<int> m_p; shared_ptr<float> m_q;public: SPResourceClass() : m_p(new int), m_q(new float) { } // Implicitly defined dtor is OK for these members, // shared_ptr will clean up and avoid leaks regardless.};
  • 永遠(yuǎn)通過值傳遞方式用throw拋出異常,通過引用傳遞用catch來捕獲異常。

  • 可以拋出基本類型也可以拋出對象,啥都可以

  • catch(...)可以捕獲所有異常

  • catch過程中不會(huì)觸發(fā)隱式類型轉(zhuǎn)換

  • 異常被拋出,但是直到main函數(shù)也沒有被catch,就會(huì)std::terminate()

  • c++不像java,不會(huì)強(qiáng)制檢查異常,throw了外層即使沒有catch也會(huì)編譯通過

  • 異常被拋出時(shí),在catch之前,try和throw之間的所有局部對象都會(huì)被析構(gòu)

  • 如果一個(gè)成員函數(shù)不會(huì)產(chǎn)生任何異常,可以使用noexcept關(guān)鍵字修飾

  • 通過throw可以重新拋出異常

    int main(){ try { try { throw 20; } catch (int n) { cout << "Handle Partially "; throw; //Re-throwing an exception } } catch (int n) { cout << "Handle remaining "; } return 0;}

    小測驗(yàn)

    你真的理解異常處理了嗎,我們可以做幾道測驗(yàn)題:

    看這幾段代碼會(huì)輸出什么:

    測試代碼1:

    #include <iostream>using namespace std; int main(){ int x = -1;  // Some code cout << "Before try \n"; try { cout << "Inside try \n"; if (x < 0) { throw x; cout << "After throw (Never executed) \n"; } } catch (int x ) { cout << "Exception Caught \n"; }  cout << "After catch (Will be executed) \n"; return 0;}

    輸出:

    Before tryInside tryException CaughtAfter catch (Will be executed)

    throw后面的代碼不會(huì)被執(zhí)行

    測試代碼2:

    #include <iostream>using namespace std; int main(){ try { throw 10; } catch (char *excp) { cout << "Caught " << excp; } catch (...) { cout << "Default Exception\n"; } return 0;}

    輸出:

    Default Exception

    throw出來的10首先沒有匹配char*,而catch(...)可以捕獲所有異常。

    測試代碼3:

    #include <iostream>using namespace std; int main(){ try { throw 'a'; } catch (int x) { cout << "Caught " << x; } catch (...) { cout << "Default Exception\n"; } return 0;}

    輸出:

    Default Exception

    'a'是字符,不能隱式轉(zhuǎn)換為int型,所以還是匹配到了...中。

    測試代碼4:

    #include <iostream>using namespace std; int main(){ try { throw 'a'; } catch (int x) { cout << "Caught "; } return 0;}

    程序崩潰,因?yàn)閽伋龅漠惓V钡絤ain函數(shù)也沒有被捕獲,std::terminate()就會(huì)被調(diào)用來終止程序。

    測試代碼5:

    #include <iostream>using namespace std; int main(){ try { try { throw 20; } catch (int n) { cout << "Handle Partially "; throw; //Re-throwing an exception } } catch (int n) { cout << "Handle remaining "; } return 0;}

    輸出:

    Handle Partially Handle remaining

    catch中的throw會(huì)重新拋出異常。

    測試代碼6:

    #include <iostream>using namespace std; class Test {public: Test() { cout << "Constructor of Test " << endl; } ~Test() { cout << "Destructor of Test " << endl; }}; int main() { try { Test t1; throw 10;} catch(int i) { cout << "Caught " << i << endl;}}

    輸出:

    Constructor of TestDestructor of TestCaught 10

    在拋出異常被捕獲之前,try和throw中的局部變量會(huì)被析構(gòu)。

    小總結(jié)

    異常處理對于錯(cuò)誤的處理更簡潔,可以更方便的把錯(cuò)誤信息反饋給調(diào)用者,同時(shí)不需要調(diào)用者使用額外的ifelse分支來處理成功或者不成功的情況。如果不是特別特別注重實(shí)時(shí)性能或者特別在乎程序的體積我們完全可以使用異常處理替代我們平時(shí)使用的c語言中的那種錯(cuò)誤碼處理方式。

    關(guān)于c++的異常處理就介紹到這里,你都了解了嗎?大家有問題可以

    參考資料

    https://www.zhihu.com/question/22889420
    https://isocpp.org/wiki/faq/
    https://docs.microsoft.com/en-us/cpp/cpp/errors-and-exception-handling-modern-cpp?view=vs-2019
    https://blog.csdn.net/zhangyifei216/article/details/50410314
    https://www.runoob.com/cplusplus/cpp-exceptions-handling.html
    https://www.geeksforgeeks.org/exception-handling-c/


    本文授權(quán)轉(zhuǎn)載自公眾號(hào)“程序喵大人”,作者程序喵大人


    -END-




    推薦閱讀



    【01】C++ 基礎(chǔ)知識(shí)!初學(xué)者必看!
    【02】C++ 轉(zhuǎn) Python 這三年,我都經(jīng)歷了什么?
    【03】C 語言會(huì)比 C++ 快?
    【04】2019年C++有哪些發(fā)展?
    【05】C++編程中的核心知識(shí)點(diǎn)


    免責(zé)聲明:整理文章為傳播相關(guān)技術(shù),版權(quán)歸原作者所有,如有侵權(quán),請聯(lián)系刪除

    免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場,如有問題,請聯(lián)系我們,謝謝!

    嵌入式ARM

    掃描二維碼,關(guān)注更多精彩內(nèi)容

    本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時(shí)聯(lián)系本站刪除。
    換一批
    延伸閱讀

    9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

    關(guān)鍵字: 阿維塔 塞力斯 華為

    加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

    關(guān)鍵字: AWS AN BSP 數(shù)字化

    倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

    關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

    北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

    關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

    8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

    關(guān)鍵字: 騰訊 編碼器 CPU

    8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

    關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

    8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

    關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

    要點(diǎn): 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競爭優(yōu)勢...

    關(guān)鍵字: 通信 BSP 電信運(yùn)營商 數(shù)字經(jīng)濟(jì)

    北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

    關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

    北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡稱"軟通動(dòng)力")與長三角投資(上海)有限...

    關(guān)鍵字: BSP 信息技術(shù)
    關(guān)閉
    關(guān)閉