1. 异常处理的思想

(1)异常处理原理

大程序中函数之间有着复杂的分工和调用关系,发现错误的函数并不一定具备解决函数的能力,此时则引发异常,希望他的调用者能够捕获这个异常。若调用者不能解决这个错误,还可继续向上级汇报,直到异常被处理为止。若始终没有处理异常,最终会被C++运行系统处理掉。

(2)异常的传播方向

(3)异常处理机制的优势

C++的异常处理机制使得异常的处理和引发不必在同一个函数上,这样的底层函数可以更加实际地解决问题,而不必过多考虑对异常的处理,设计程序可以在上层调用者中的恰当位置设计对不同类型异常的处理。

2. 异常处理的语法

(1)案例:处理除0异常

被调用函数

int divide(int x,int y)//
{
    if(y==0)
        throw x;
    return x/y;
}

int mian()//调用者
{
    try{
        cout<<"5/2="<<divide(5,2)<<endl;
        cout<<"3/0="<<divide(3,0)<<endl;
    }catch(int e){//e接收到函数中的x
        cout<<e<<"is divide by zero"<<endl;
    }
    cout<<"That is OK."<<endl;
    return 0;
}

当divide函数进行过程中,遇到了异常时(throw前的if语句),本身没有处理这个异常,所以调用终止,回到main函数的调用点,当catch子句恰好与抛出的异常类型匹配,异常将在这里被捕获,并执行catch中的内容。

(2)原理

运行到达try语句->引起异常->抛出点在try字句内->catch语句依次查找是否匹配

  • 抛出点不在try字句内/抛出的异常与各catch子句类型皆不匹配->结束当前函数

执行,回到当前函数的调用点,把调用点当成异常的抛出点,然后重复这一过程

  • 如果始终没有找到匹配的catch子句,main函数结束执行,调用库中的terminate函数

(terminate的默认功能是终止程序)

  • 如果找到了匹配的catch子句,catch中的语句执行,try块也执行完毕

(3)细节

throw与catch匹配的三种情况

a. catch子句中声明的异常类型就是抛出异常对象的类型或引用

b. catch子句中声明的异常类型就是抛出异常对象的类型的公共基类或其引用

c. 抛出的异常类型和catch子句中声明的异常类型皆为指针类型,且前者到后者可隐含转换

(4)异常接口说明

可以在函数的声明中列出它可能抛掷出的所有错误类型

void fun() throw(A,B,C,D);

也可以不抛出任何的异常

void fun() throw();//等价于void fun()

这时遇到异常会调用unexpected函数,该函数的缺省行为是调用terminate函数中止程序。也可以自定义unexpected函数(已成为历史)

3. 异常处理的构造和析构

(1)构造与析构的过程

在程序中找到一个匹配的catch异常语句匹配时,如果catch子句的异常声明是一个值参数,初始化方式是复制被抛出的异常对象;如果是引用,则初始化方式是使该引用指向异常对象。异常抛出后栈的展开过程开始,从进入try块到异常被抛出前,这期间在栈上构造(且未被析构)的对象会被自动析构,顺序与构造相反,这一过程称为栈的解旋。

(2)异常声明中的参数

很多情况下可以省略参数,在catch语句中只声明特定类型的异常,但是如果要在catch中访问某个对象则需要传入参数

4. 不带操作数的throw

用一个不带操作数的表达式可以使当前正在被处理的异常被再次抛出,这样一个表达式只能出现在一个catch的子句或catch子句内部调用的函数中。再次抛出的异常对象是源异常对象(不是副本)

try{
    throw MyExpectation("some expresion");
}catch
{
    //...
    throw;
}