C++異常

pg_dog發表於2017-04-22

今天我們來說說C++中有關異常的處理方法,異常的常見用法及特性。
一,有關錯誤的處理方法

  1. 返回錯誤碼;
  2. 終止程式;
  3. 返回合法值,讓程式處於某種非法的狀態;
  4. 呼叫一個預先設定好出現錯誤時呼叫的函式——回撥函式;
  5. 異常處理;
    當一個函式發現自己無法解決的錯誤時丟擲異常,讓函式的呼叫者直接或間接的解決這個問題。

二,異常的丟擲和捕獲

  1. 異常是通過丟擲物件來引發的,該物件的型別決定應該啟用哪個處理程式碼。
  2. 被選中的處理程式碼是呼叫鏈中與該物件型別匹配且距離丟擲異常位置最近的那一個。
  3. 丟擲物件後會釋放區域性儲存物件,所以被丟擲的物件也就還給系統了。throw表示式會初始化一個丟擲特殊的異常物件副本(匿名物件),異常物件由編譯管理,異常物件在傳給catch處理之後撤銷。

三,棧展開
丟擲異常的時候,將暫停當前函式的執行,開始查詢對應的匹配catch字句。
首先,檢查throw是否在try塊內部,如果是在查詢匹配的catch語句;
如果有匹配的就進行處理,沒有則退出當前函式棧,繼續在呼叫函式的棧中進行查詢;
不斷重複上述過程,直到main函式的棧,如果沒有找到,則終止程式。
上述這個沿著呼叫鏈查詢匹配的catch字句的過程稱為棧展開。如下圖所示:
這裡寫圖片描述

注:找到匹配的catch字句後,會繼續沿著catch字句後面繼續執行。

四,異常捕獲的匹配規則

異常物件的型別與catch說明符的型別必須完全相同。
但是以下情況例外:

  1. 允許非const物件向const物件的轉換;(大範圍->小範圍)
  2. 允許派生型別向基類型別的轉換;(切割)
  3. 將陣列轉換為指向陣列的指標,將函式轉換為指向函式的指標。
#include<iostream>
#include<string>
using namespace std;

class Exception
{
public:
    Exception(int errID,const char* errMsg)
        :_errID(errID),_errMsg(errMsg)
    {}

    void What() const
    {
        cout << "errID:" << _errID << endl;
        cout << "errMsg:" << _errMsg << endl;
    }
private:
    int _errID;   //錯誤碼
    string _errMsg;   //錯誤資訊
};

void Fun1(bool isThrow)
{
    if (isThrow)
    {
        throw Exception(1, "丟擲異常Exception");
        printf("Fun1(%d)\n",isThrow);
    }
}

void Fun2(bool isThrowString, bool isThrowInt)
{
    if (isThrowString)
    {
        throw string("丟擲string物件");
    }

    if (isThrowInt)
    {
        throw 7;
    }
    printf("Fun2(%d,%d)\n", isThrowString, isThrowInt);
}

void Func()
{
    try
    {
        Fun1(true);
        Fun2(true,true);
    }
    catch (const string& errMsg)
    {
        cout << "Catch string Object:" << errMsg << endl;
    }

    catch (int errID)
    {
        cout << "Catch int Object:" << errID << endl;
    }

    catch (const Exception& e)
    {
        e.What();
    }

    catch (...)
    {
        cout << "未知異常" << endl;
    }

    printf("Func()\n");
}

int main()
{
    Func();
    system("pause");
    return 0;
}

五,異常的重新丟擲

有可能單個catch不能完全處理一個異常,在進行一些校正處理以後,希望再交給更外層的呼叫鏈函式來處理,catch則可以通過重新丟擲將異常傳遞給更上層的函式來處理。

class Exception
{
public:
    Exception(int errID=0,const char* errMsg="")
        :_errID(errID),_errMsg(errMsg)
    {}

    void What()
    {
        cout << _errID << endl;
        cout << _errMsg << endl;
    }
protected:
    int _errID;
    string _errMsg;
};

void Fun1()
{
    throw string("Throw Fun1 string");
}
void Fun2()
{
    try
    {
        Fun1();
    }
    catch (string & errMsg)
    {
        cout << errMsg << endl;
    }
}
void Fun3()
{
    try
    {
        Fun2();
    }
    catch (Exception &e)
    {
        e.What();
    }
}

int main()
{
    Fun3();
    system("pause");
    return 0;
}

六,異常與(建構函式與解構函式)

1,建構函式完成物件的構造和初始化,需要保證的是,不要在建構函式中丟擲異常,否則可能導致物件的不完整或沒有完全初始化。
2,解構函式主要是完成資源的清理,需要保證的是,不要在解構函式中丟擲異常,否則可能導致資源洩漏。

七,異常相比於返回錯誤碼的優缺點
優點:

  1. 能準確清楚地表達錯誤原因。而錯誤碼只是整型,不能描述錯誤的原因;
  2. 很多第三方庫在使用異常,關閉異常將導致難以與之結合;
  3. 再測試框架中,異常好用;

缺點:

 1,異常打亂執行流,影響除錯分析程式碼;
 2,異常有異常安全的問題,需配合RAII使用;

相關文章