使用标准异常


在〈捕捉自定义异常〉中自定义了异常类,实际上,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;
    }
};

withdrawdeposit可以改为:

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;
}




展开阅读全文