簡介例外處理


在〈定義類別〉中,Accountdepositwithdraw,在參數值 amount 不正確時,都是直接顯示文字模式下的訊息後直接 return,如果這個類別不只使用在文字模式中呢?

這就需要有個方式,令 depositwithdraw 的呼叫方程式碼,可以得知引數不正確,方式之一是透過傳回值,例如,令 depositwithdraw 可以傳回 int,以不同的傳回值代表發生了什麼問題,呼叫方必須檢查傳回值,知道方法是否順利執行成功,並在傳回值代表發生錯誤時予以處理,基於 C 風格的錯誤處理,就是這麼一回事。

不過,因為 C/C++ 的傳回值只能有一個,若方法本身就會傳回執行結果,例如,若 withdraw 本身就會傳回提領的數目,型態為 double,那麼就無法再以傳回 int 值來通知錯誤是否發生,當然,透過適當的設計,還是可以達到通知錯誤的效果,例如傳回負的 double 表示提領失敗,或者是以類別封裝提領款項與錯誤碼,令 withdraw 傳回該類別實例,透過檢查實例中代表錯誤的值域來確認執行成功與否,若成功則提取實例中的款項欄位,若失敗進行錯誤處理。

然而,有時開發者會忘了要檢查錯誤,程式也可能因此在發生錯誤時,繼續往下執行,因而在後續發生不可預期的結果;另一方面,也許會希望有一種方式,在錯誤發生時,直接中斷流程,而且傳播錯誤,在每個上層的呼叫點都中斷,直到有某個呼叫點處理錯誤為止。

當然,這也可以透過逐層撰寫檢查錯誤的程式碼來達成,不過,若想令傳播錯誤,在每個上層的呼叫點都中斷,直到有某個呼叫點處理錯誤為止。這件事成為某些誤發生時的預設行為呢?

C++ 可以藉由拋出錯誤來達到,錯誤值可以是任何型態,例如拋出錯誤訊息的字串:

...略

void Account::deposit(double amount) {
    if(amount <= 0) {
        throw "必須存入正數";
    }
    this->balance += amount;
}

void Account::withdraw(double amount) {
    if(amount > this->balance) {
        throw "餘額不足";
    }
    this->balance -= amount;
}

被拋出的錯誤稱為例外(exception),在呼叫 depositwithdraw 時,若指定了錯誤的引數,例外會被拋出,執行流程就會中斷:

Account acct = {"123-456-789", "Justin Lin", 1000};
cout << acct.to_string() << endl;

acct.deposit(-500); // terminate called after throwing an instance of 'char const*'

實際執行執行程式時,流程從拋出例外後就整個中斷了,程式整個掛點,若想處理被拋出的例外,可以使用 try-catch 語法,例如:

Account acct = {"123-456-789", "Justin Lin", 1000};

try {
    acct.deposit(-500);
}
catch(char const* error) {
    cout << error << endl;  // 顯示「必須存入正數」
}

可能拋出例外的程式碼,可以撰寫在 try 區塊之中,如果沒有發生錯誤,執行完 try 區塊後,就不會執行 catch 區塊,若執行時發生錯誤,流程會從例外拋出處中斷,然後對應的 catch 區塊可以指定例外型態來捕捉,例外捕捉後會指定給括號中的變數,接著執行 catch 區塊。

Account 是某程式庫的類別,由於錯誤發生時的處理方式並沒有寫死,而是拋出例外,讓呼叫方自行決定是否捕捉處理;這邊在文字模式執行時,捕捉例外後顯示錯誤值;想像一下,如果 Account 被用於圖形介面環境,捕捉例外後就可以操作圖形介面,來顯示相對應的錯誤訊息。