捕捉自訂例外


在〈簡介例外處理〉示範了如何以字串作為例外拋出,catch 例外時型態指明為 char const*,這表示相同型態的例外被拋出時,會執行對應的 catch 區塊,如果有多個型態的話,可以指定多個 catch

try {
    foo();
}
catch(int errCode) {
    ...
}
catch(char const* errorMsg) {
    ...
}

只不過直接 catch 基本型態之類的例外,在程式的意圖上不清楚,而且這類例外可以表現的資訊有限,int 型態的例外被捕捉後,還得比對錯誤碼,字串的例外被捕捉後,不一定只是想顯示字串,可能還要比對字串內容等,以做其他相應的處理。

你可以自訂例外類型,並令類型名稱具有代表例外意涵的實質意義,例如:

class Exception {
    const string message;

public:
    explicit Exception(const string &message) : message(message) {}

    virtual const char* what() {
        return this->message.c_str();
    }

    virtual ~Exception() = default;
};

class InvalidArgument : public Exception {
    using Exception::Exception;
};

class Insufficient : public Exception {
    int balance;

public:
    explicit Insufficient(const string &message, int balance) 
                : Exception(message), balance(balance) {}

    int getBalance() {
        return balance;
    }
};

這麼一來,就可以依不同的例外型態來捕捉例外實例了:

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

try {
    acct.withdraw(10200);
    acct.deposit(-500);
}
catch(InvalidArgument &ex) {
    cout << "引數錯誤:" << ex.what() << endl;
}
catch(Insufficient &ex) {
    cout << "帳號錯誤:"  << endl
         << "\t" << ex.what() << endl
         << "\t餘額 " << ex.getBalance() << endl;
}

catch 時若型態有繼承關係,父型態不能寫在子型態的 catch 之前,因為會先被比對到,導致後續的子型態 catch 寫了等於沒寫,編譯時也會提出警訊:

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

try {
    acct.withdraw(10200);
    acct.deposit(-500);
}
catch(Exception &ex) {
    cout << ex.what() << endl;
}
// warning: exception of type 'InvalidArgument' will be caught by earlier handler for 'Exception'
catch(InvalidArgument &ex) {
    cout << "引數錯誤:" << ex.what() << endl;
}
catch(Insufficient &ex) {
    cout << "帳號錯誤:"  << endl
         << "\t" << ex.what() << endl
         << "\t餘額 " << ex.getBalance() << endl;
}

如果例外的型態具有相同父類,那可以將父類寫在最後,作為同類型例外的捕遺:

try {
    acct.withdraw(10200);
    acct.deposit(-500);
}
catch(InvalidArgument &ex) {
    cout << "引數錯誤:" << ex.what() << endl;
}
catch(AccountException &ex) {
    cout << "帳號操作錯誤:" << ex.what() << endl;
}
catch(Exception &ex) {
    cout << ex.what() << endl;
}

C++ 有個全捕捉語法 catch(...),用來捕捉全部型態的例外,不過並不鼓勵使用,這之後再來談。