1. 程式人生 > >38、不一樣的C++系列--C++的異常處理

38、不一樣的C++系列--C++的異常處理

C++的異常處理

異常處理介紹

C++內建了異常處理的語法元素 try … catch …

  • try語句處理正常程式碼邏輯
  • catch語句處理異常情況
  • try語句中的異常由對應的catch語句處理
  • 語法:
try
{
    double r = divide(1, 0);
}
catch(...)
{
    cout << "Divided by zero..." << endl;
}
  • C++通過throw語句丟擲異常資訊
double divide(double a, double b)
{
    const double delta = 0.0000000000001
; double ret = 0; if(!((-delta < b) && (b < delta))) { ret = a / b; } else { //產生除0異常 throw 0; } return ret; }

異常處理分析

  • 這裡對C++異常處理分析一下:
    • throw丟擲的異常必須被catch處理
      • 當前函式能夠處理異常,程式繼續往下執行
      • 當前函式無法處理異常,則函式停止執行,並返回

未被處理的異常會順著函式呼叫棧向上傳播,知道被處理為止,否則程式將停止執行。

function1 ==> function2 ==> function3
          <==           <== throw1;

這裡舉一個例子:

#include <iostream>
#include <string>

using namespace std;

double divide(double a, double b)
{
    const double delta = 0.000000000000001;
    double ret = 0;

    if( !((-delta < b) && (b < delta)) )
    {
        ret = a / b;
    }
    else
{ //用throw丟擲異常資訊 throw 0; } return ret; } int main(int argc, char *argv[]) { try { double r = divide(1, 0); cout << "r = " << r << endl; } catch(...) { //try語句中丟擲的異常在這裡接受並處理 cout << "Divided by zero..." << endl; } return 0; }

繼續學習 try… catch …的知識點:

  • 同一個try 語句可以跟上多個catch語句

    • catch語句可以定義具體處理的異常型別
    • 不同型別的異常由不同的catch語句負責處理
    • try語句中可以丟擲任何型別的異常
    • catch( … ) 用於處理所有型別的異常
    • 任何異常都只能被捕獲(catch)一次
  • 異常處理的匹配規則:

//異常處理匹配時,不進行任何的型別轉換
|   try
|   {
|       throw 1;
|   }   
|   catch(Type1 t1)
|   {
|   }
|   catch(Type2 t2)
|   {
|   }
|   catch(TypeN tn)
|   {
|   }
v
異常丟擲後,自上而下嚴格匹配每一個catch語句處理的型別

這裡用一個例子來試驗一下匹配規則:

#include <iostream>
#include <string>

using namespace std;

void Demo1()
{
    try
    {   
        //這裡丟擲一個字元
        throw 'c';
    }
    catch(char c)
    {
        cout << "catch(char c)" << endl;
    }
    catch(short c)
    {
        cout << "catch(short c)" << endl;
    }
    catch(double c)
    {
        cout << "catch(double c)" << endl;
    }
    catch(...)
    {
        cout << "catch(...)" << endl;
    }
}

void Demo2()
{
    //這裡丟擲string類字串
    throw string("D.T.Software");
}

int main(int argc, char *argv[])
{    
    Demo1();

    try
    {
        Demo2();
    }
    catch(char* s)
    {
        cout << "catch(char *s)" << endl;
    }
    catch(const char* cs)
    {
        cout << "catch(const char *cs)" << endl;
    }
    catch(string ss)
    {
        cout << "catch(string ss)" << endl;
    }

    return 0;
}

執行結果如下:

catch(char c)
catch(string ss)

catch再丟擲異常

在try … catch … 語句中,catch語句塊中可以丟擲異常

try
{
    func();
}
catch(int i)
{
    //將捕獲的異常重新丟擲
    throw i;
}
catch(...)
{
    //將捕獲的異常重新丟擲
    throw;
}

//catch中丟擲的異常需要外層的try ... catch ...捕獲

可是為什麼要重新丟擲異常呢?因為:

catch中捕獲的異常可以被重新解釋後丟擲,這樣就可以在工程開發中使用這樣的方式統一異常型別。
//工程開發
通過呼叫 MyFunc 獲得 func函式的功能和統一的異常資訊
    |
    | 呼叫
    V

//私有庫
void MyFunc(int i);
/*異常型別為Exception*/
    |
    | 封裝
    V

//第三方庫
void func(int i);
/*異常型別為int*/

這裡舉一個例子:

#include <iostream>
#include <string>

using namespace std;

void Demo()
{

    try
    {
        try
        {
            //這裡丟擲的異常由內層try ... catch ...來捕獲處理
            throw 'c';
        }
        catch(int i)
        {
            cout << "Inner: catch(int i)" << endl;
            //這裡再次丟擲的異常由外層來捕獲並處理
            throw i;
        }
        catch(...)
        {
            cout << "Inner: catch(...)" << endl;
            //這裡再次丟擲的異常由外層來捕獲並處理
            throw;
        }
    }
    catch(...)
    {
        cout << "Outer: catch(...)" << endl;
    }
}

/*
    假設: 當前的函式式第三方庫中的函式,因此,我們無法修改原始碼

    函式名: void func(int i)
    丟擲異常的型別: int
                        -1 ==》 引數異常
                        -2 ==》 執行異常
                        -3 ==》 超時異常
*/
void func(int i)
{
    if( i < 0 )
    {
        throw -1;
    }

    if( i > 100 )
    {
        throw -2;
    }

    if( i == 11 )
    {
        throw -3;
    }

    cout << "Run func..." << endl;
}

void MyFunc(int i)
{
    try
    {
        func(i);
    }
    catch(int i)
    {
        switch(i)
        {
            case -1:
                throw "Invalid Parameter";
                break;
            case -2:
                throw "Runtime Exception";
                break;
            case -3:
                throw "Timeout Exception";
                break;
        }
    }
}

int main(int argc, char *argv[])
{
    Demo();

    try
    {
        MyFunc(11);
    }
    catch(const char* cs)
    {
        cout << "Exception Info: " << cs << endl;
    }

    return 0;
}

執行結果為:

Inner: catch(...)
Outer: catch(...)
Exception Info: Timeout Exception

自定義異常型別

自定義類型別及匹配:

  • 異常的型別可以是自定義類型別
  • 對於類型別異常的匹配依舊是自上而下嚴格匹配
  • 賦值相容性原則在異常匹配中依然適用
  • 一般而言
    • 匹配子類異常的catch放在上部
    • 匹配父類異常的catch放在下部

工程中的異常類:

  • 在工程中會定義一系列的異常類
  • 每個類代表工程中可能出現的一種異常型別
  • 程式碼複用時可能需要重解釋不同的異常類
  • 在定義 catch語句塊時推薦使用引用作為引數

這裡舉一個例子:

#include <iostream>
#include <string>

using namespace std;

class Base
{
};

class Exception : public Base
{
    int m_id;
    string m_desc;
public:
    Exception(int id, string desc)
    {
        m_id = id;
        m_desc = desc;
    }

    int id() const
    {
        return m_id;
    }

    string description() const
    {
        return m_desc;
    }
};


/*
    假設: 當前的函式式第三方庫中的函式,因此,我們無法修改原始碼

    函式名: void func(int i)
    丟擲異常的型別: int
                        -1 ==》 引數異常
                        -2 ==》 執行異常
                        -3 ==》 超時異常
*/
void func(int i)
{
    if( i < 0 )
    {
        throw -1;
    }

    if( i > 100 )
    {
        throw -2;
    }

    if( i == 11 )
    {
        throw -3;
    }

    cout << "Run func..." << endl;
}

void MyFunc(int i)
{
    try
    {
        func(i);
    }
    catch(int i)
    {
        switch(i)
        {
            case -1:
                //這裡直接丟擲一個類
                throw Exception(-1, "Invalid Parameter");
                break;
            case -2:
                //這裡直接丟擲一個類
                throw Exception(-2, "Runtime Exception");
                break;
            case -3:
                //這裡直接丟擲一個類
                throw Exception(-3, "Timeout Exception");
                break;
        }
    }
}

int main(int argc, char *argv[])
{
    try
    {
        MyFunc(11);
    }
    //接受到的時候 判斷為引用型別
    catch(const Exception& e)
    {
        cout << "Exception Info: " << endl;
        cout << "   ID: " << e.id() << endl;
        cout << "   Description: " << e.description() << endl;
    }
    //接受到的時候 判斷為引用型別
    catch(const Base& e)
    {
        cout << "catch(const Base& e)" << endl;
    }

    return 0;
}

執行結果為:

Exception Info: 
   ID: -3
   Description: Timeout Exception

C++標準庫的異常類族

在C++標準庫中提供了實用異常類族

  • 標準庫中的異常都是從 exception 類派生的
  • exception 類有兩個主要的分支
    • logic_error
      • 常用於程式中的可避免邏輯錯誤
    • runtime_error
      • 常用於程式中無法避免的惡性錯誤

這裡寫圖片描述

這裡演示一下如何使用:

#include <iostream>
#include <string>
#include "Array.h"
#include "HeapArray.h"

using namespace std;

void TestArray()
{
    Array<int, 5> a;

    /*
        這裡如果越界會丟擲異常:
        throw out_of_range("T& Array<T, N>::operator[] (int index)");
        throw out_of_range("T Array<T, N>::operator[] (int index) const");
        */
    for(int i=0; i<a.length(); i++)
    {
        a[i] = i;
    }

    for(int i=0; i<a.length(); i++)
    {
        cout << a[i] << endl;
    }
}

void TestHeapArray()
{
    HeapArray<double>* pa = HeapArray<double>::NewInstance(5);

    if( pa != NULL )
    {
        HeapArray<double>& array = pa->self();

        /*
        這裡如果越界會丟擲異常:
        throw out_of_range("T& HeapArray<T>::operator [] (int index)");
        throw out_of_range("T HeapArray<T>::operator [] (int index) const");
        */
        for(int i=0; i<array.length(); i++)
        {
            array[i] = i;
        }

        for(int i=0; i<array.length(); i++)
        {
            cout << array[i] << endl;
        }
    }

    delete pa;
}

int main(int argc, char *argv[])
{

    try
    {
        TestArray();

        cout << endl;

        TestHeapArray();
    }
    catch(...)
    {
        cout << "Exception" << endl;
    }

    return 0;
}

小結

  • C++中直接支援異常處理的概念
  • try … catch …是C++中異常處理的專用語句
  • try 語句處理正常程式碼邏輯,catch 語句處理異常情況
  • 同一個 try 語句可以跟上多個 catch 語句
  • 異常處理必須嚴格匹配,不進行任何的型別轉換
  • catch語句塊中可以丟擲異常
  • 異常的型別可以是自定義類型別
  • 賦值相容性原則在異常匹配中依然適用
  • 標準庫中的異常都是從exception類派生的