在〈捕捉自訂例外〉中自訂了例外類別,實際上,C++ 標準程式庫在 exception
標頭定義了基礎類別 exception
與一些處理例外的函式,而 stdexcept
標頭中定義了一系列繼承自 exception
的例外類別:
logic_error
invalid_argument
domain_error
length_error
out_of_range
future_error(C++11)
bad_optional_access(C++17)
runtime_error
range_error
overflow_error
underflow_error
regex_error(C++11)
system_error(C++11)
ios_base::failure(C++11)
filesystem::filesystem_error(C++17)
tx_exception(TM TS)
nonexistent_local_time(C++20)
ambiguous_local_time(C++20)
format_error(C++20)
bad_typeid
bad_cast
bad_any_cast(C++17)
bad_weak_ptr(C++11)
bad_function_call(C++11)
bad_alloc
bad_array_new_length(C++11)
bad_exception
ios_base::failure(until C++11)
bad_variant_access(C++17)
這份清單來自〈std::exception〉,那麼該選用哪個呢?〈捕捉自訂例外〉中自訂了 InvalidArgument
,似乎可以用 invalid_argument
來取代,那麼 Insufficient
呢?看來沒有對應的類別,那該繼承哪個來自訂例外類別呢?
例外若被 catch
捕捉,只要 catch
處理後沒有拋出例外,後續的流程是可以繼續的,然而,有些例外就算被 catch
捕捉了,最好是別再繼續流程,最多就是留下日誌(logging),然後令程式崩潰,因為這類例外最好的處理方式,是找出引發例外的程式碼,直接修正程式碼,避免重新執行程式再度拋出例外。
例如,記憶體配置方面的例外 bad_alloc
、轉型方面的例外 bad_cast
等,這些就該是只留下日誌、令程式停止,修正程式碼,而不是在執行時期嘗試回復程式的執行流程,在以上例外列表除了 logic_error
與其子類別之外,其他第一層或其下子類的例外,都是屬於這類例外,如果想自訂這類例外,建議繼承 runtime_error
。
另外有些例外,是屬於商務邏輯上的錯誤範範,例如餘額不足,其實是商務邏輯上的考量,這類錯誤可以繼承 logic_error
,該類別或其子類實例被拋出,是可以捕捉後嘗試回復執行流程,例如顯示餘額不足後,重新請使用者輸入提領金額。
(是執行時期錯誤還是商務邏輯上的錯誤,有時不見得那麼容易分辨,例如,同樣是標準程式庫提供的例外類別,有些語言會將引數錯誤視為執行時期錯誤,然而 C++ 是歸類在邏輯錯誤。)
就〈捕捉自訂例外〉中的 Insufficient
,可以算是商務邏輯上的錯誤,可以繼承標準程式庫的 logic_error
來自訂:
class Insufficient : public logic_error {
int balance;
public:
explicit Insufficient(const string &message, int balance)
: logic_error(message), balance(balance) {}
int getBalance() {
return balance;
}
};
而 withdraw
、deposit
可以改為:
void Account::deposit(double amount) {
if(amount <= 0) {
throw invalid_argument("必須是正數");
}
this->balance += amount;
}
void Account::withdraw(double amount) {
if(amount <= 0) {
throw invalid_argument("必須是正數");
}
if(amount > this->balance) {
throw Insufficient("餘額不足", this->balance);
}
this->balance -= amount;
}
執行時可以如下撰寫:
Account acct = {"123-456-789", "Justin Lin", 1000};
cout << acct.to_string() << endl;
try {
acct.withdraw(10200);
acct.deposit(-500);
}
catch(invalid_argument &ex) {
cout << "引數錯誤:" << ex.what() << endl;
}
catch(Insufficient &ex) {
cout << "帳號錯誤:" << endl
<< "\t" << ex.what() << endl
<< "\t餘額 " << ex.getBalance() << endl;
}