C++基礎(chǔ)6:異常

為什么需要異常?

  • 異常機(jī)制的處理原理
    程序會(huì)出現(xiàn)錯(cuò)誤,尤其是不易察覺(jué)的錯(cuò)誤。需要了解并解決這些錯(cuò)誤。通常,程序出現(xiàn)錯(cuò)誤,都會(huì)強(qiáng)制退出,很難排除錯(cuò)誤原因。

C語(yǔ)言如何表示錯(cuò)誤

  1. 函數(shù)返回值
    • 通常,成功返回0,返回值-1
    • 返回值為指針類型,成功返回非NULL,失敗返回值NULL
      例如:malloc();例外shmat()失敗返回值為MAP_INVALD(-1)
    • 其它另類的返回值
      fread()/fwrite()返回讀寫字符長(zhǎng)度size_t,超出長(zhǎng)度表示失敗。
  2. 全局變量errno

異常處理特點(diǎn)

異常提供一個(gè)錯(cuò)誤專用通道。
優(yōu)點(diǎn):

  1. 不干擾正常的返回值。
  2. 必須處理異常。

案例

通過(guò)命令行計(jì)算兩個(gè)數(shù)字相除。

#include <iostream>
#include <sstream>
using namespace std;

int main(int argc,char* argv[]){
    istringstream iss(argv[1]); // 讀取第一個(gè)數(shù)字
    int a(0);
    iss >> a;
    iss = argv[2]; // 讀取第二個(gè)數(shù)字
    int b(0);
    iss >> b;
    cout<< a/b << endl;// 輸出數(shù)字相除
}

語(yǔ)法

異常分為兩個(gè)部分:拋出異常與捕獲并處理異常。

  • 拋出異常
throw 表達(dá)式;
  • 捕獲并處理異常
try {  
        // 保護(hù)代碼 包含可能拋出異常的語(yǔ)句;  
} catch (類型名 [形參名]) {  
        // catch塊 處理異常
}  

特點(diǎn)

  1. 只要拋出異常,異常后的代碼不再執(zhí)行。
  2. 異常的所拋出與經(jīng)過(guò)的棧都會(huì)銷毀。

異常機(jī)制try-throw-catch的目標(biāo)是問(wèn)題檢測(cè)與問(wèn)題解決分離

復(fù)雜一點(diǎn)地寫法

try {  
        // 保護(hù)代碼 包含可能拋出異常的語(yǔ)句;  
} catch (類型名1 [形參名]) {  
        // catch塊 處理異常
} catch (類型名2 [形參名]) {  
        // catch塊 處理異常
} catch (類型名3 [形參名]) {  
        // catch塊 處理異常
} catch(...){
        // catch塊 處理異常
}

注意

  • 異常捕獲具有類型匹配,只有相同的或者父類類型才能匹配到。
  • 如果多個(gè)catch都能接受相同異常,只有最前面的一個(gè)可以接收到。
    catch(...)只能放在所有異常捕獲的最后

異常的接口聲明/異常規(guī)范

返回值類型 函數(shù)() throw(異常列表);

指定函數(shù)可以拋出何種異常,如果沒(méi)有throw(異常列表)默認(rèn)可以拋出所有異常。
指定函數(shù)不拋出函數(shù),異常列表為空throw()

那么當(dāng)異常拋出后新對(duì)象如何釋放?

異常處理機(jī)制保證:異常拋出的新對(duì)象并非創(chuàng)建在函數(shù)棧上,而是創(chuàng)建在專用的異常棧上,因此它才可以跨接多個(gè)函數(shù)而傳遞到上層,否則在棧清空的過(guò)程中就會(huì)被銷毀。所有從trythrow語(yǔ)句之間構(gòu)造起來(lái)的對(duì)象的析構(gòu)函數(shù)將被自動(dòng)調(diào)用。但如果一直上溯到main函數(shù)后還沒(méi)有找到匹配的catch塊,那么系統(tǒng)調(diào)用terminate()終止整個(gè)程序,這種情況下不能保證所有局部對(duì)象會(huì)被正確地銷毀。

舉例

  1. 捕獲異常
#include <iostream>
using namespace std;

void test(){
    cout << "before throw." << endl;
    throw -1;
    cout << "after throw." << endl;
}
int main(){
    try{
        test();
    }catch(int a){
        cout << "exception:" << a << endl;
    }
}
  1. 異常與局部對(duì)象析構(gòu)
#include <iostream>
using namespace std;

class Test{
public:
    Test(){
        cout << "Test Init" <<endl;
    }
    ~Test(){
        cout << "Test Destroy" <<endl;
    }
};

int main(){
    try{
        Test t;
        cout << "before throw." << endl;
        throw -1;
        cout << "after throw." << endl;
    }catch(int a){
        cout << "exception:" << a << endl;
    }
}

注意事項(xiàng)

  1. 如果拋出的異常一直沒(méi)有函數(shù)捕獲(catch),則會(huì)一直上傳到c++運(yùn)行系統(tǒng)那里,導(dǎo)致整個(gè)程序的終止。
  2. 一般在異常拋出后資源可以正常被釋放,但注意如果在類的構(gòu)造函數(shù)中拋出異常,系統(tǒng)是不會(huì)調(diào)用它的析構(gòu)函數(shù)的,處理方法是:如果在構(gòu)造函數(shù)中要拋出異常,則在拋出前要記得刪除申請(qǐng)的資源。
  3. 異常處理僅僅通過(guò)類型而不是通過(guò)值來(lái)(switch-case)匹配的,所以catch塊的參數(shù)可以沒(méi)有參數(shù)名稱,只需要參數(shù)類型。
  4. 函數(shù)原型中的異常說(shuō)明要與實(shí)現(xiàn)中的異常說(shuō)明一致,否則容易引起異常沖突。
  5. 應(yīng)該在throw語(yǔ)句后寫上異常對(duì)象時(shí),throw先通過(guò)Copy構(gòu)造函數(shù)構(gòu)造一個(gè)新對(duì)象,再把該新對(duì)象傳遞給 catch.
  6. catch塊的參數(shù)推薦采用地址傳遞而不是值傳遞,不僅可以提高效率,還可以利用對(duì)象的多態(tài)性。另外,派生類的異常撲獲要放到父類異常撲獲的前面,否則,派生類的異常無(wú)法被撲獲。
  7. 編寫異常說(shuō)明時(shí),要確保派生類成員函數(shù)的異常說(shuō)明和基類成員函數(shù)的異常說(shuō)明一致,即派生類改寫的虛函數(shù)的異常說(shuō)明至少要和對(duì)應(yīng)的基類虛函數(shù)的異常說(shuō)明相同,甚至更加嚴(yán)格,更特殊。

標(biāo)準(zhǔn)異常類

exception派生

異常類 作用
logic_error 邏輯錯(cuò)誤,在程序運(yùn)行前可以檢測(cè)出來(lái)
runtime_error 運(yùn)行時(shí)錯(cuò)誤,僅在程序運(yùn)行中檢測(cè)到

邏輯異常logic_error派生

異常類 作用
domain_error 違反了前置條件
invalid_argument 指出函數(shù)的一個(gè)無(wú)效參數(shù)
length_error 指出有一個(gè)超過(guò)類型size_t的最大可表現(xiàn)值長(zhǎng)度的對(duì)象的企圖
out_of_range 參數(shù)越界
bad_cast 在運(yùn)行時(shí)類型識(shí)別中有一個(gè)無(wú)效的dynamic_cast表達(dá)式
bad_typeid 報(bào)告在表達(dá)試typeid(*p)中有一個(gè)空指針p

運(yùn)行時(shí)runtime_error派生

異常類 作用
range_error 違反后置條件
bad_alloc 存儲(chǔ)分配錯(cuò)誤

嘗試捕獲邏輯異常和運(yùn)行時(shí)異常

自定義異常類

  • 編碼流程
    1.繼承異常類exception
    2.實(shí)現(xiàn)接口what()
  • 代碼結(jié)構(gòu)
class 異常類:public exception {    
public:    
   const char* what()const throw() {
        return 信息字符串;    
   }        
}; 

構(gòu)造函數(shù)、析構(gòu)函數(shù)的異常處理

  • 構(gòu)造函數(shù)可以拋出異常,此時(shí)不會(huì)調(diào)用析構(gòu)函數(shù),所以如果拋出異常前,申請(qǐng)了資源,需要自己釋放。
  • C++標(biāo)準(zhǔn)指明析構(gòu)函數(shù)不能、也不應(yīng)該拋出異常。
  • C++標(biāo)準(zhǔn)規(guī)定,構(gòu)造函數(shù)失敗,析構(gòu)函數(shù)不會(huì)執(zhí)行。就是說(shuō)在構(gòu)造函數(shù)拋出異常前分配的資源將無(wú)法釋放。
  1. 如果析構(gòu)函數(shù)拋出異常,則異常點(diǎn)之后的程序不會(huì)執(zhí)行,如果析構(gòu)函數(shù)在異常點(diǎn)之后執(zhí)行了某些必要的動(dòng)作比如釋放某些資源,則這些動(dòng)作不會(huì)執(zhí)行,會(huì)造成諸如資源泄漏的問(wèn)題。
  2. 通常異常發(fā)生時(shí),c++的機(jī)制會(huì)調(diào)用已經(jīng)構(gòu)造對(duì)象的析構(gòu)函數(shù)來(lái)釋放資源,此時(shí)若析構(gòu)函數(shù)本身也拋出異常,則前一個(gè)異常尚未處理,又有新的異常,會(huì)造成程序崩潰的問(wèn)題。

是否使用異常機(jī)制

為什么很多經(jīng)典書籍鼓勵(lì)使用異常,但是實(shí)際開(kāi)發(fā)中很多C++編碼規(guī)范卻禁用異常?
C++異常機(jī)制在語(yǔ)法上是更加優(yōu)雅的處理錯(cuò)誤,但是實(shí)際上編譯出來(lái)的程序會(huì)有一些性能損失,另外錯(cuò)誤地使用異常處理代碼會(huì)變得更加復(fù)雜。

編譯器選項(xiàng)

g++特殊編譯選項(xiàng)g++ -fno-exceptions

在不同的編碼規(guī)范中,對(duì)是否使用異常存在爭(zhēng)議。

C語(yǔ)言實(shí)現(xiàn)異常機(jī)制

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,541評(píng)論 1 51
  • 1. 讓自己習(xí)慣C++ 條款01:視C++為一個(gè)語(yǔ)言聯(lián)邦 為了更好的理解C++,我們將C++分解為四個(gè)主要次語(yǔ)言:...
    Mr希靈閱讀 2,864評(píng)論 0 13
  • 重新系統(tǒng)學(xué)習(xí)下C++;但是還是少了好多知識(shí)點(diǎn);socket;unix;stl;boost等; C++ 教程 | 菜...
    kakukeme閱讀 20,032評(píng)論 0 50
  • 接著上節(jié) condition_varible ,本節(jié)主要介紹future的內(nèi)容,練習(xí)代碼地址。本文參考http:/...
    jorion閱讀 14,844評(píng)論 1 5
  • “突然好想見(jiàn)你呀” “我馬上到老家了” 不知今天怎么心血來(lái)潮想要見(jiàn)X先生,所以產(chǎn)生了上面的對(duì)話。當(dāng)看到X先生說(shuō)他回...
    扒小怪閱讀 549評(píng)論 0 1