假设有A,B,C三个函数,其中A调用了B,B调用了C(A > B > C)。
如果C执行过程中出现异常,那么首先要将异常情况返回给B,然后由B再返回给A。
在C++中,可以使用异常来处理这种情况。
可以简单概括为:函数A捕捉函数C发出的异常。
分析一下这句话:
修改代码,在函数C中抛出一个异常,异常值为1;对应的,在函数A中会捕捉这个异常,并且将异常值输出。
- void C()
- {
- throw 1;
- }
-
- void B()
- {
- C();
- }
-
- void A()
- {
- try {
- B();
- } catch (int i)
- {
- cout << "catch exception " << i << endl;
- }
- }
main函数
- int main(int argc, char **argv)
- {
- A();
- return 0;
- }
编译测试,可以看到函数A成功捕捉到了函数C抛出的异常。
修改C函数,增加一个传参,在代码中根据传参的值选择程序正常执行还是抛出异常。
- void C(int i)
- {
- int a = 1;
- double b = 1.23;
- float c = 4.56;
-
- if (i == 0) {
- cout << "In C, it is OK" << endl;
- } else if (i == 1) {
- throw a;
- } else if (i == 2) {
- throw b;
- } else if (i == 3) {
- throw c;
- }
- }
C的传参是由A传给B,B传给C的,也就是说来源是A。
- void B(int i)
- {
- cout << "call C ..." << endl;
- C(i);
- cout << "After call C" << endl;
- }
-
- void A(int i)
- {
- try {
- B(i);
- } catch (int j) {
- cout << "catch exception " << j << endl;
- }
- }
修改main函数,将传入的argv[1]从中字符转为整型,然后传给函数A,也就是说C的传参来源是命令行。
- int main(int argc, char **argv)
- {
- int i;
-
- if (argc != 2) {
- cout << "Usage: " << endl;
- cout << argv[0] << " <0|1|2|3>" << endl;
- return -1;
- }
-
- i = strtoul(argv[1], NULL, 0);
-
- A(i);
-
- return 0;
- }
编译测试,当执行 ./exception2 0 时,程序正常执行,没有发生异常。
当执行 ./exception2 1 时,捕捉到了异常,并且输出了异常值1。
当执行 ./exception2 2 时,程序崩溃了。
这是因为,传入2时C抛出了一个double型的异常值,但是A捕捉的异常是int型,此时程序崩溃了。
我们需要完善一下测试程序,当C函数抛出不同类型的异常值时,程序不应该崩溃。
修改A函数,增加对double型和float型异常值的捕捉和处理。
此时再执行 ./exception3 2 和 ./exception3 3 就没有问题了。
但是,如果异常的类型非常的多,难道每个类型都要加一个专门的处理吗?是否可以写一段代码,直接处理剩下的所有种类的异常?
答:可以的。
修改A函数,将对float型异常值的处理,改为对剩下的所有种类的异常的处理。
编译测试,此时对float型异常值的处理流程,就是省略号中定义的处理了。
对 double 和 int 则没有影响。
在之前的程序中,throw出的是int,double,float,那么是否可以扔出类的实例化对象呢?
答:可以的。
增加一个类,叫做MyException:
- class MyException {
- public:
- void what(void)
- {
- cout << "MyException::what(void)" << endl;
- }
- };
然后在C函数中,传参为4时抛出这个类异常。
在A函数中,捕捉到抛出了MyException类异常时,调用成员函数what。
编译测试,程序成功抛出和捕捉到了异常。
修改代码,增加一个 MyException类的派生类 MySubException。
- class MySubException : public MyException {
- public:
- void what(void)
- {
- cout << "MySubException::what(void)" << endl;
- }
- };
修改代码,当i=5时,抛出的类异常是MySubException。
此时如果不修改A函数,即不添加MySubException类异常的检测时,此时会触发哪个处理程序。
可以看到,此时会触发处理基类异常的异常处理程序,也就是说基类的检测,同样可以检测出抛出的派生类异常。
在A函数中,添加对MySubException类异常的处理。
添加在对MyException类异常的处理之后。
此时编译会有警告,意思是抛出的派生类MySubException类异常,会被之前处理基类MyException类异常的程序处理,而不是处理MySubException异常的代码来处理,因为catch MySubException在catch MyException之后。
执行发现,触发的处理程序也是MyException类的处理程序。
将对MySubException类异常的处理,转移到对MyException类异常的处理之前。
此时编译执行,可以成功触发对MySubException类异常的处理。
修改代码,去掉对派生类MySubException异常的处理,只保留对基类MyException的处理。
此时,如果想要实现,抛出基类MyException类异常,调用的是基类的what函数;抛出派生类MySubException类异常,调用派生类的what函数。
那么,就要使用多态。
将MyException类的成员函数what,声明为 virtual 即可。
编译测试,结果符合预期。
前面的代码,在C函数中抛出异常,在函数A中捕获异常并处理,那么,A怎么知道有哪些异常需要处理呢?或者说,它怎么知道函数C可能抛出哪些异常呢?
修改代码,在函数C中增加说明可能抛出的异常的说明,声明函数C可能抛出int型和double型的异常。
编译测试,此时只有抛出int型和double型的异常时,程序可以成功执行,抛出其他异常,程序都会崩溃。
也就是说,即时在A函数中有针对float型,MyException类,MySubException类异常的处理,但是由于C函数没有声明可能抛出这些异常,此时如果C函数抛出这些异常,程序也会崩溃。
当程序检测到未定义异常时,如果我们没有定义一个未定义异常的处理函数,那么程序会调用一个默认的未定义异常的处理函数。
可以通过set_unexpected重新定义一个未定义异常的处理函数。
修改代码,增加一个未定义异常的处理函数my_unexpected_func。
编译测试,可以看到当检测到未定义异常时,调用了我们自定义的未定义异常的处理函数my_unexpected_func。
在上面的未定义异常的处理函数函数中,除了输出我们添加的my_unexpected_func(void)语句外,还有一条“terminate called after throwing an instance of 'MyException'”。
这个是由默认的未定义异常终止函数输出的,类似的,我们同样可以自定义一个未定义异常终止函数。
修改代码,使用set_terminate自定义一个未定义异常终止函数my_terminate_func。
编译测试,可以看到,在调用了我们自定义的未定义异常的处理函数之后,系统又调用了自定义的未定义异常的终止函数。
对于意料之外的异常,系统会执行两个函数:
其中,“unexpected”函数用来处理声明之外的异常;“terminate”函数用来处理“catch分支未提到的异常”。