当前位置: 首页 > 科技观察

深入理解C++中的异常处理机制

时间:2023-03-18 13:56:38 科技观察

异常处理增强的错误恢复能力是提高代码健壮性最有力的方法之一。C语言中使用的错误处理方法被认为是紧耦合的,函数的使用者必须编写非常接近函数调用的错误处理代码,这使得它笨拙且难以使用。C++中引入了异常处理机制,这是C++的主要特点之一,是一种更好的思考问题和处理错误的方式。使用错误处理可以带来一些好处,如下:错误处理代码的编写不再繁琐,也不再与正常代码混在一起。程序员只需要编写自己想要生成的代码,然后在单独的区域编写,在段落中编写和处理错误是不是错了?如果多次调用同一个函数,只需要在某处写一次错误处理代码即可。如果函数必须向调用者发送一次错误消息,则不能忽略错误。它将抛出一个描述错误的对象。传统的错误处理和异常处理在讨论异常处理之前,先说说C语言中传统的错误处理方式。下面是三种方法:在函数中返回一个错误,该函数将设置一个全局错误状态标志。将signals作为信号处理系统,在函数中提出signals,通过signal设置信号处理函数。这种方法耦合度非常高,不同库生成的信号值可能会发生冲突。在标准C库中使用non-local。跳转函数setjmp和longjmp,这里使用setjmp和longjmp来演示如何处理错误:#include#includejmp_bufstatic_buf;//用来存储处理器上下文,用来跳转voiddo_jmp(){//dosomething,simetimeoccursalittleerror//调用longjmp后会加载static_buf的处理器信息,然后第二个参数为setjmp函数的返回值作为返回点longjmp(static_buf,10);//10为错误码,执行根据这个错误码进行相应的处理}intmain(){intret=0;//将处理器信息保存在static_buf中并返回0,相当于在这里做一个标记,后面可以跳过if((ret=setjmp(static_buf))==0){//要执行的代码do_jmp();}else{//发生错误if(ret==10)std::cout<<"alittleerror"<#includeusingnamespacestd;classbase{public:base(){cout<<"baseconstructfunccall"<usingnamespacestd;intmain(){try{throw'a';}catch(inta){cout<<"int"<#include#includeusingnamespacestd;classMyError{constchar*constdata;public:MyError(constchar*constmsg=0):data(msg){//idle}};voiddo_error(){throwMyError("somethingbadhappend");}//自定义终止函数,函数原型需要一致voidterminator(){cout<<"I'llbeback"<#includeusingnamespacestd;classbase{public:base(){cout<<"Istarttoconstruct"<usingnamespacestd;intmain()try{throw"main";}catch(constchar*msg){cout<#include#includeusingnamespacestd;classMyError:publicruntime_error{public:MyError(conststring&msg=""):runtime_error(msg){}};//runtime_errorlogic_error都是继承自标准异常,带字符串构造函数//intmain(){try{throwMyError("mymessage");}catch(MyError&x){cout<#include#include#includeusingnamespacestd;classUp{};classFit{};voidg();//异常规范,f函数只能抛出Up和Fit类型的异常voidf(inti)throw(Up,Fit){switch(i){case1:throwUp();case2:throwFit();}g();}voidg(){throw47;}voidmy_ternminate(){cout<<"Iamaternminate"<Tstack::pop(){if(count==0)throwlogic_error("stackunderflow");elsereturndata[--count];}如果函数在最后一行抛出异常,那么这会导致函数不返回堆叠的元素,但是Count已经减1,所以函数所需的栈顶元素丢失。本质原因是因为这个函数试图同时做两件事,1.返回值,2.改变栈的状态。***把这两个独立的动作放到两个独立的函数中,遵循内聚设计原则,每个函数只做一件事。下面再讨论异常安全的另一个问题,就是如何编写非常常见的赋值运算符,如何保证赋值操作异常安全。classBitmap{...};classWidget{...private:Bitmap*pb;};Widget&Widget::operator=(constWidget&rhs){deletepb;pb=newBitmap(*rhs.pb);return*this;}上面的代码确实不带自赋值安全,如果rhs是对象本身,会导致*rhs.pb指向一个删除的对象。然后准备改进。添加身份测试。Widget&Widget::operator=(constWidget&rhs){If(this==rhs)return*this;//身份测试deletepb;pb=newBitmap(*rhs.pb);return*this;}但是现在上面的代码还是不满足异常安全,因为如果执行deletepb后执行newBitmap时发生异常,最终会指向一块被删除的内存。现在只要稍作改动,上面的代码就可以成为异常安全的。Widget&Widget::operator=(constWidget&rhs){If(this==rhs)return*this;//IdentityTestBitmap*pOrig=pb;pb=newBitmap(*rhs.pb);//现在即使这里出现异常,它不会影响this指向的对象。deletepOrig;return*this;}这个例子看起来比较简单,但是还是很有用的。对于赋值运算符,很多情况下都需要重载。