C++例外處理機制

2020-07-16 10:04:41
錯誤測試通常是涉及 if 語句或其他控制機制的簡單過程。例如,以下程式碼段會在發生被零除錯誤之前捕獲該錯誤:
if (denominator == 0)
    cout << "ERROR: Cannot divide by zero. n";
else
    quotient = numerator / denominator;
但如果類似的程式碼是返回商的函數的一部分,如下例所示:
//不可靠的除非函數
double divide(double numerator, double denominator)
{
    if (denominator == 0)
    {
        cout << "ERROR: Cannot divide by zero.n";
        return 0;
    }
    else
        return numerator / denominator;
}
函數通常通過返回一個預定的值來發出錯誤信號。在以上範例中,當嘗試被零除時,函數返回 0。然而,這是不可靠的,因為 0 是除法操作的有效結果。即使函數顯示錯誤訊息,呼叫該函數的程式部分也不會知道何時發生了錯誤。因此,像這樣的問題需要更複雜的錯誤處理技術。

丟擲異常

處理複雜錯誤情況的一種方法是使用異常。異常是指示錯誤的值或物件。當錯誤發生時,一個異常被 "丟擲",因為控制權將傳遞給程式負責捕獲和處理該型別錯誤的那一部分。

例如,以下程式碼顯示了修改後的 divide 函數,當試圖除零時將丟擲異常:
double divide(double numerator, double denominator)
{
    if (denominator == 0)
        throw string("ERROR: Cannot divide by zero.n");
    else
        return numerator / denominator;
}
以下語句將導致異常被丟擲:

throw string("ERROR: Cannot divide by zero.n");

throw 關鍵字後跟一個引數,可以是任何值。實際上,引數的型別將用於確定錯誤的性質。上面的函數只是丟擲一個包含描述性錯誤資訊的字串物件。

包含 throw 語句的行稱為丟擲點。當執行一個 throw 語句時,控制權傳遞給稱為例外處理程式的另一部分程式。

處理異常

為了處理異常,程式必須有一個 try/catch 結構。try/catch 結構的一般格式如下:
try
{
    //此處呼叫可能丟擲異常的
    //函數或物件成員函數
}
catch(exception parameter)
{
    //此處處理異常
}
//根據需要重複catch程式碼段
構造的第一部分是 try 塊。它從關鍵字 try 開始,然後是任何可能直接或間接導致拋出異常的執行語句的程式碼塊。try 塊之後緊跟著一個或多個 catch 塊,它們是例外處理程式。一個 catch 塊以關鍵字 catch 開始,隨後是一對圓括號,裡面包含異常引數宣告。

例如,下面就是一個 try/catch 結構,它可以與 divide 函數一起使用:
try
{
    quotient = divide(num1, num2);
    cout<< "The quotient is " << quotient << endl;
}
catch (string exceptionString)
{
    cout << exceptionString;
}
由於 divide 函數丟擲的是一個型別為字串的異常,因此必須有一個捕獲字串的例外處理程式。顯示的 catch 塊會捕獲 exceptionString 形參中的錯誤訊息,然後使用 cout 顯示它。

現在來看一個完整的程式,了解 throw、try 和 catch 是如何一起合作的。在下面程式的第一次範例執行中,給出的是有效的資料,所以顯示了程式在沒有錯誤情況下的正常執行結果;在第二次範例執行中,給出的分母是 0,於是顯示丟擲異常的結果。
//This program illustrates exception handling.
#include <iostream>
#include <string>
using namespace std;

// Function prototype
double divide(double, double);

int main()
{
    int num1, num2;
    double quotient;
    cout << "Enter two numbers:";
    cin >> num1 >> num2;
    try
    {
        quotient = divide(num1, num2);
        cout << "The quotient is " << quotient << endl;
    }
    catch (string exceptionString)
    {
        cout << exceptionString;
    }
    cout << "End of the program.n";
    return 0;
}
double divide(double numerator, double denominator)
{
    if (denominator == 0)
        throw string ("ERROR: Cannot divide by zero. n");
    else
        return numerator / denominator;
}
程式輸出結果:

Enter two numbers:12 0
ERROR: Cannot divide by zero.
End of the program.

從輸出結果中可以看出,異常導致程式跳出 divide 函數並進入 catch 塊。在 catch 塊完成之後,程式繼續執行在 try-catch 結構之後第一個語句。

異常未被捕獲的情形

有兩種可能的方式導致丟擲的異常未被捕獲。第一種可能性是程式未包含具有正確資料型別的異常形參的 catch 塊。第二種可能性是異常在 try 塊外部丟擲。在這兩種情況下,異常都會導致整個程式中止執行。

物件導向的例外處理類

在理解了 C++ 中的異常機制工作方式之後,現在來探討一下物件導向的例外處理方法。先來看一個 IntRange 類。
//IntRange.h 的內容
#ifndef INTRANGE_H
#define INTRANGE_H

#include <iostream>
using namespace std;

class IntRange
{
    private:
        int input; // For user input
        int lower; // Lower limit of range
        int upper; // Upper limit of range
    public:
        // Exception class
        class OutOfRange
        { }; // Empty class declaration
        // Member functions
        IntRange(int low, int high) // Constructor
        {
            lower = low;
            upper = high;
        }
        int getInput()
        {
            cin >> input;
            if (input < lower || input > upper)
                throw OutOfRange();
            return input;
        }
};
#endif
IntRange 是一個簡單的類,其成員函數 getInput 允許使用者輸入一個整數值。該值與成員變數 lower 和 upper(由類建構函式初始化)進行比較。如果輸入的值小於 lower 或大於 upper,則丟擲異常,表示該值超出範圍。否則,該值將從函數返回。

該函數不是丟擲一個字串或一個原始型別的值,而是丟擲一個異常類。請注意在 public 部分中出現的空類宣告:

class OutOfRange
{ };//空類宣告

請注意,這個類沒有成員。該類唯一重要的部分是它的名字,這個名字將被例外處理程式碼使用。請看 getinput 函數中的if語句:
if (input < lower || input > upper)
    throw OutOfRange();
throw 語句的引數 OutOfRange() 會使 OutOfRange 類的範例被建立並作為異常拋出。剩下的就是由一個 catch 塊來處理異常。以下是一個範例:
catch (IntRange::OutOfRange)
{
    cout << "That value is out of range. n";
}
所有必須出現在 catch 塊括號內的是異常類的名稱。異常類是空的,所以不需要宣告一個實際的引數。catch 塊需要知道的只是異常的型別。

由於 OutOfRange 類是在 IntRange 類中宣告的,因此其名稱必須完全符合作用域解析運算子。下面的程式顯示了在驅動模組程式中起作用的類。
//This program demonstrates the use of obj ect-oriented exception handling.
#include <iostream>
#include "IntRange.h"
using namespace std;

int main()
{
    IntRange range(5, 10);
    int userValue;
    cout << "Enter a value in the range 5-10: ";
    try {
        userValue = range.getInput();
        cout << "You entered " << userValue << endl;
    }
    catch (IntRange::OutOfRange)
    {
        cout << "That value is out of range. n";
    }
    cout << "End of the program.n";
    return 0;
}
程式輸出結果:

Enter a value in the range 5-10: 12
That value is out of range.
End of the program.