C++异常处理:try、throw、catch

发布时间 2023-08-24 23:25:25作者: 无夜千幕雪

1.引子

程序在运行时,总是会遇到一些错误,这些错误或者是导致程序无法运行,例如操作空指针,或是不符合正常运行的规律,例如除以0。

因此,在C++程序当中就必须添加对应异常处理机制,在检测到指定的程序异常时,为保证程序正常运行,需要跳转至异常处理程序当中。

常规的错误处理有以下几种解决方案:

2.abort()

在遇到错误时,可以调用abort函数

abort函数位于标准库的头文件cstdlib当中,它会向标准错误流发送一个程序异常终止消息,然后终止程序。

int main() {
    abort();
    return 0;
}

当然,这种方式实际上并不灵活,abort的调用会直接终止程序,并不是我们所想要的结果。通常我们都会建立故障诊断机制,所以abort的使用频率比较低。

3.try throw catch

C++自带有异常处理机制,提供了将控制权从程序的一个部分转移到另一个部分的途径,这种对异常的处理方法包含三部分:

try:标识其中特定的异常可能被激活的代码块。

throw:当出现异常时抛出异常,跳转至处理部分。

catch:捕获异常,针对不同的异常类型采取对应的措施

基本写法为:

try{
    //Error e("error!");
    //throw e;
    throw 1;
    //throw 1.5;
    //throw Error ("error!");
}
catch (double){

}
catch (int){

}
catch (Error& e){

}

其中,抛出异常和捕获异常有多种写法,可以适用于不同的情况。

3.1.抛出异常

异常抛出有以下几种写法:

直接抛出数值,会根据值的类型自动做隐式转换,例如int和double,这种用法弊端比较大,一般不怎么使用:

throw 1;    // 相当于throw (int)1;
throw 1.5;     // 相当于throw (double)1.5;

构造并抛出异常值,如果异常被封装为类,则会用构造函数对异常进行构造。当然也可以先构造一个类出来,然后将这个类抛出,两种做法是一样的。

class Error{
    public:
        Error(const char* message) 
            : m_message(message)
        { };
private:
        string m_message;
};

throw Error("error!");    // 1.调用构造函数后抛出

Error e("error!");
throw e;    // 2.抛出构造后的值

3.2.捕获异常

异常捕获有以下几种写法:

类型捕获,依据throw抛出的类型进行选择最终进入哪一个catch模块,多个catch连成一片类似于else if,只不过catch检测的是类型。如果没有抛出异常,那么不会进入任何一个catch块当中。

catch (int){ 
}
catch (double){ 
}
catch (Error){ 
}

类型+返回值捕获,返回的不只是类型,实际上还隐藏了返回值,可以自定义返回值的名称将其显式表示,并将其运用到catch模块当中。

catch (Error& error){ 
 cout << error << endl;
}

捕获其它异常,"..."这种写法相当于else,任何其它类型的异常都会被该catch捕获。

同理,如果没有写catch(...)就相当于没有写else,那么当程序因为异常抛出,但是没有找到对应类型的异常时,则不会执行任何catch内的操作。

catch (...){
}

3.3.嵌套

多个异常模块可以嵌套使用,但在任意位置抛出异常时,程序会逐级选择catch模块,直到找到对应的catch块或者跳出所有嵌套的try块为止,中间其它try当中的内容,都会被忽略。

例如下面的程序:

输出double异常时,跳转至catch(double)中,继续运行外层try当中的内容,从而输出1、2、3、4、6;

输出int异常时,跳转至catch(int)中,无视了外层try块剩下的内容,从而输出1、2、5 2、6;

int main() {

    try {
        std::cout << "1..." << endl;
        try {
            std::cout << "2..." << endl;
            //double error = 2;    //如果抛出double,则输出1、2、3、4、6
            int error = 2;    //如果抛出int,则输出1、2、5 2、6
            throw error;    
        }
        catch (double ) {
            std::cout << "3..." << endl;
        }
        std::cout << "4..." << endl;
    }
    catch (int& error) {
        std::cout << "5..." << error << endl;
    }
    std::cout << "6..." << error << endl;
    return 0;
}

4.故障码

虽然这一节主要讲的是try throw catch,但实际上这种异常解决方案用的还是不算多,最常使用的依然是故障码。

故障码不是啥新鲜的东西,设置一个全局或较高层级的故障类,用以记录故障的类型。在每个程序模块运行时对故障进行检测,同时在运行过程中出现异常时对故障进行对应的填充。

在C语言当中,只能用结构体指针的方式进行传输,不够方便,而C++以类的形式进行封装更有利于这种方式的异常处理。

其伪代码如下所示:

Error e;

if(e == null){
    ...
    if(){
        e = error;
    }
}
...

故障码有个优势,那就是相比于try catch的结构,可以比较容易地对程序进行控制,对于异常的处理更加的精细化,因此很多时候用的比较多的反而是这种解决方案。