C++异常处理

引言:

     异常处理(exception handling)是C++的一项语言机制,用于在程序能处理异常事件。因为程序的执行过程中会遇到许多不可预知的错误事件,譬如说分配内存失败,文件找不到,内存越界,数组越界等等。这些错误事件存在着很大的隐患,程序员必须在程序中对这些错误进行处理,那么我们需要在程序中不断的添加if语句,来判断异常是否出现。如果有则必须进行处理。这样依赖程序可读性就差了很多,给程序员增加了极大的工作负担。
     C++的异常处理机制则改变了这种面貌,使得真正的计算处理与错误处理分开。使得程序员能关注真正的计算处理工作。同时代码的可读性增加。
    异常事件在C++中表示为异常对象(exception obje)。异常事件发生时,由操作系统为程序设置当前异常对象,然后执行程序的当前异常处理代码块,在包含了异常出现点的最内层的try块,依次匹配同级的catch语句。如果匹配catch语句成功,则在该catch块内处理异常;然后执行当前try...catch...块之后的代码。如果在当前的try...catch...块没有能匹配该异常对象的catch语句,则由更外一层的try...catch...块处理该异常;如果当前函数内的所有try...catch...块都不能匹配该异常,则递归回退到调用栈的上一层函数去处理该异常。如果一直回退到主函数main()都不能处理该异常,则调用系统函数terminate()终止程序。异常处理仅仅通过类型而不是通过值来匹配的,Catch块中的参数可以没有参数名称,只需要参数类型,除非使用那个参数。这也是异常处理与传统的错误处理技术的区别。
    关键字:1.try 2.catch 3.throw
    其中关键字try定义了一个受到监控,受到保护的代码块。关键字catch则定义了一个当try 的代码块出现异常时,进行错误处理的程序模块。并且每个catch块都带有一个参数,参数的数据类型用于与异常对象的数据类型进行匹配。throw则是检测到一个异常错误发生后向外抛出一个异常事件,由相应的catch块进行验证异常数据类型是否匹配,并决定由哪一个进行错误处理。

了解了异常处理的基本情况,我们首先写一下代码来看一下:

         


int main()
{
try
{
cout << "try catch Example:" << endl;
throw(1);
}
catch(int value)
{
cout << "在Catch语句中,处理异常错误,异常错误的值为" << value << endl;
}
}



        程序执行结果为:


        下面我们来仔细介绍下try catch 和throw

throw:

         执行throw语句时,其操作数的结果作为对象被复制构造为一个新的对象,放在内存的特殊位置(既不是堆也不是栈,Windows上是放
在“线程信息块TIB”中)。
这个新的对象由本级的try所对应的catch语句逐个做类型匹配;如果匹配不成功,则与本函数的外层catch
语句依次做类型匹配;如果在本函数内不能与catch语句匹配成功,则递归回退到调用栈的上一层函数内从函数调用点开始继续与
catch语句匹配。重复这一过程直到与某个catch语句匹配成功或者直到主函数main()都不能处理该异常。
因此,throw语句抛出的异常对象不同于一般的局部对象。一般的局部对象会在其作用域结束时被析构。而throw语句抛出
的异常对象驻留在所有可能被激活的catch语句都能访问到的内存空间中。throw语句抛出的异常对象在匹配成功的catch语句的结束
处被析构(即使该catch语句使用的是非“引用”的传值参数类型)。

Catch:

    catch语句匹配被抛出的异常对象时,如果catch语句的参数是引用型,则该参数直接引用到throw语句抛出的异常对象上;
如果catch参数是传值的,则拷贝构造一个新的对象作为catch语句的参数的值。在该catch语句结束时,先析构catch的传值的参数对
象,然后析构throw语句抛出的异常对象。
    catch语句匹配异常对象时,不会做任何隐式类型转换(implicit type conversion),包括类型提升(promotion)。 异常对
象与catch语句进行匹配的规则很严格,一般除了以下几种情况外,异常对象的类型必须与catch语句的声明类型完全匹配:允许非
const到const的转换;允许派生类到基类的转换;将数组和函数类型转换为对应的指针。
我们针对catch语句中的参数来分情况写下代码,使用引用型参数和值参数。代码:

class Student
{
private:
    int age;
    string name;
public:
    Student()
    {
        cout << "Student Created!" << endl;
        age = 0;
        name = "";
    }

    ~Student()
    {
        cout << "Student Deleted!" << endl;
    }

};
int main()
{
    try
    {
        cout << "try catch Example:" << endl;
        Student stu;
        throw(stu);
    }
    catch(Student &value)//引用传递
    {
        cout << "在Catch语句中,处理异常错误"   <<  endl;

    }
    return 0;

}
首先是使用引用型参数,执行结果:


       

然后我们将catch语句做了修改。修改为如下:

catch(Student value)//采用值传递
执行结果:


       得到在值传递的时候有3个对象被析构了。同理是3个异常对象被创建。而在引用传递中,只有2个对象被析构,即2个对象被创建。我们注意到throw解释中有这么一句话:执行throw语句时,其操作数的结果作为对象被复制构造为一个新的对象,放在内存的特殊位置(既不是堆也不是栈,Windows上是放在“线程信息块TIB”中)。

 Student stu;//这里创建一个对象,
        throw(stu);//这里使用的stu 是复制过后的一个对象,存放在线程信息块TIB
        //然后我们在使用值传递的时候,仍然需要队这个新创建的对象复制,如果是采用
        //引用传递则直接操作这个对象。

栈展开:

      

      栈展开(unwinding)是指当前的try...catch...块匹配成功或者匹配不成功异常对象后,从try块内异常对象抛出位置,到try块的开始处的所有已经执行了各自构造函数的局部变量,按照构造生成顺序的逆序,依次被析构。如果当前函数内匹配不成功异常对象,则从最外层的try语句到当前函数体的起始位置处的局部变量也依次被逆序析构,实现栈展开,然后再回退到调用栈的上一层函数内从函数调用点开始继续处理该异常。
catch语句如果匹配异常对象成功,在完成了对catch语句的参数的初始化(对传值参数完成了参数对象的copy构造)之后,对同层级的try块执行栈展开。
     我们可以总结下异常对象的创建与析构过程:
     1.首先throw异常对象的时候,其操作数的结果作为对象被复制构造为一个新的对象,放在内存的特殊位置(既不是堆也不是栈,Windows上是放在“线程信息块TIB”中)。(这里复制了一次)一般的局部对象会在其作用域结束时被析构。而throw语句抛出的异常对象驻留在所有可能被激活的catch语句都能访问到的内存空间中。
     2.catch语句匹配被抛出的异常对象时,如果catch语句的参数是引用型,则该参数直接引用到throw语句抛出的异常对象上;如果catch参数是传值的,则拷贝构造一个新的对象作为catch语句的参数的值。(这里也复制了一个对象)在该catch语句结束时,先析构catch的传值的参数对象,然后析构throw语句抛出的异常对象。
     3.当前的try...catch...块匹配成功或者匹配不成功异常对象后,从try块内异常对象抛出位置,到try块的开始处的所有已经执行了各自构造函数的局部变量,按照构造生成顺序的逆序,依次被析构。
写一下代码看一下:

class Student1
{
private:
    int age;
    string name;
public:

    Student1()
    {
        cout << "Student1 Created!" << endl;
        age = 0;
        name = "";
    }
    Student1(const Student1& stu)
    {
        age = stu.age;
        name = stu.name;

        cout << " Student1 Copyed!" << endl;
    }

    ~Student1()
    {
        cout << "Student1 Deleted!" << endl;
    }

};
class Student2
{
private:
    int age;
    string name;
public:
    Student2()
    {
        cout << "Student2 Created!" << endl;
        age = 0;
        name = "";
    }

    ~Student2()
    {
        cout << "Student2 Deleted!" << endl;
    }

};
int main()
{
    try
    {
        cout << "try catch Example:" << endl;
        Student1 stu;
        Student2 stu2;
        throw(stu);
     
    }
    catch(Student1 value)
    {
        cout << "在Catch语句中,处理异常错误"   <<  endl;

    }
    return 0;

}

执行结果:


 try
    {
        cout << "try catch Example:" << endl;
        Student1 stu;//这里创建一个对象Student1,
        Student2 stu2;//这里创建一个对象Student2
        throw(stu);//这里throw抛出的异常对象的stu 是复制过后的一个对象,存放在线程信息块TIB
        //然后我们在使用值传递的时候,需要抛出一个stu对象复制之后的对象,如果是采用
        //引用传递则直接操作这个对象。
    }
    catch(Student1 value)//采用值传递(这里也会复制一个Student1对象)(匹配成功之后,就会执行栈展开,开始析构try中的stu和stu2)
    {
        cout << "在Catch语句中,处理异常错误"   <<  endl;

    }

try catch的嵌套分层处理

            上面说到trycatch块是可以嵌套分层的,并且通过异常对象的数据类型来进行匹配,以找到正确的catch block异常错误处理代码。下面是详细叙述如何通过异常对象的数据类型来进行匹配找到正确的catch block的过程。

        1 首先在抛出异常的trycatch块中查找catch block,按顺序先是与第一个catch block块匹配,如果抛出的异常对象的数据类型与catch block中传入的异常对象的临时变量(就是catch语句后面参数)的数据类型完全相同,或是它的子类型对象,则匹配成功,进入到catch block中执行;否则到二步;
   (2 如果有二个或更多的catch block,则继续查找匹配第二个、第三个,乃至最后一个catch block,如匹配成功,则进入到对应的catch block中执行;否则到三步;
   (3 返回到上一级的trycatch块中,按规则继续查找对应的catch block。如果找到,进入到对应的catch block中执行;否则到四步;
   (4 再到上上级的trycatch块中,如此不断递归,直到匹配到顶级的trycatch块中的最后一个catch block,如果找到,进入到对应的catch block中执行;否则程序将会执行terminate()退出。

    Example:

       

void Func()
{
    //最内层的try catch
    try
    {
        cout << "内层try catch准备抛出一个int数据类型的异常对象" << endl;
        throw 1;
    }
    //在内层的catch 中不能匹配,需要到调用Func函数的上层函数中匹配catch
    catch(float &value)
    {
        cout << " 在catch block中,处理float类型异常对象" << endl;
    }
}

int main()
{
    try
    {
        Func();
        cout << "外层try block,准备抛出double数据类型异常对象" << endl;
        throw 0.5;
    }

    catch(double &value)
    {
        cout << "外层try catch处理double类型异常对象类型" << endl;
    }
    catch(int &value)
    {
        cout << "外层try catch处理int类型异常对象类型" << endl;
    }
    return 0;
}

        执行结果:(可以看到被外层try catch接收到了) 

    C++标准中规定,嵌套的trycatch块是可以跨越函数作用域的,可以在程序任何地方throw一个异常对象。并不要求一定只能是在受到try block监控保护的作用域中才能抛出异常 ,它可以是调用这个函数的上层函数中存在 trycatch 块,这样这个函数的代码也同样是受保护、受监控的代码;即便是上层调用函 数不存在 trycatch 块,也只是不能找到处理这类异常对象错误处理的 catch block 而已,这个时候会要求系统执行terminate()来终止程序。
     Example:
void Func()
{
    throw 1;
}

int main()
{
    try
    {
        Func();
        cout << "外层try block,准备抛出double数据类型异常对象" << endl;
        throw 0.5;
    }

    catch(double &value)
    {
        cout << "外层try catch处理double类型异常对象类型" << endl;
    }
    catch(int &value)
    {
        cout << "外层try catch处理int类型异常对象类型" << endl;
    }
    Func();
    cout << "zhixing " << endl;//这里执行不到,函数终止
    return 0;
}
     这里调用了两次Func()函数,这个函数的功能是直接抛出int类型对象,可以知道第一次调用这个函数的时候,因为在trycatch块中,所以可以catch到这个异常对象,而第二次调用的时候main函数是其最上层函数,所以没有catch可以获取这个int类型异常对象,所以会执行系统的terminate()来终止程序的运行。
        执行结果:

catch(...)

       catch(…)能够捕获多种数据类型的异常对象,所以它提供给程序员一种对异常对象更好的控制手段,使开发的软件系统有很好的可靠性。就是为了有更好的安全性和可靠性,避免程序员定义的try block抛出了其它未考虑到的异常对象时导致的程序出现意外崩溃的严重后果,另外因为catch(…)能捕获系统出现的异常,而系统异常往往令程序员头痛了,现在系统一般都比较复杂,而且由很多人共同开发,一不小心就会导致一个指针变量指向了其它非法区域,结果意外灾难不幸发生了。catch(…)为这种潜在的隐患提供了一种有效的补救措施。通常程序猿处理异常会这么写:
void Func()
{
    try
    {
        //代码真正完成的工作,有可能会抛出a,b,c类型的异常
    }
    catch(a &value)
    {
        //处理a类型异常对象
    }
    catch(b &value)
    {
        //处理b类型异常对象
    }
    catch(c &value)
    {
        //处理c类型异常对象
    }
    catch(...)
    {
        //处理a b c类型之外的所有异常对象(包括系统异常)避免遗漏
    }
}



如果有什么错误,请指出,非常感谢。
 
 



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值