C++异常处理:try、throw、catch
1.引子
程序在运行时,总是会遇到一些错误,这些错误或者是导致程序无法运行,例如操作空指针,或是不符合正常运行的规律,例如除以0。
因此,在C++程序当中就必须添加对应异常处理机制,在检测到指定的程序异常时,为保证程序正常运行,需要跳转至异常处理程序当中。
常规的错误处理有以下几种解决方案:
2.abort()
在遇到错误时,可以调用abort函数。
abort函数位于标准库的头文件cstdlib当中,它会向标准错误流发送一个程序异常终止消息,然后终止程序。
当然,这种方式实际上并不灵活,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,这种用法弊端比较大,一般不怎么使用:
构造并抛出异常值,如果异常被封装为类,则会用构造函数对异常进行构造。当然也可以先构造一个类出来,然后将这个类抛出,两种做法是一样的。
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模块当中。
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的结构,可以比较容易地对程序进行控制,对于异常的处理更加的精细化,因此很多时候用的比较多的反而是这种解决方案。