C++异常处理机制
商业转载请联系作者获得授权,非商业转载请注明出处。
一、什么是异常
-异常是程序中可以检测到的不正常的情况。
-示例:被0除、数组越界、存储空间不足等。
-早期我们使用if来判断,然后进行相关处理,或者也可以使用断言。关于异常与断言这里引用了一段解释
关于异常和断言,个人以为,阐述最清楚的当属“契约式编程”。简而言之,检查前条件使用ASSERT,检查后条件使用异常。
以一个函数为例,它要求在开始执行的时候满足一系列条件,这些条件被称为“前条件”或者“先验条件”,比如,参数不为空,某全局变量应该为1,等等。不满足前条件,是不能调用此函数的,如果出现了前条件不满足仍然调用了此函数,可以认为这是一个设计错误。
检查前条件,可以使用ASSERT。这个函数执行以后,也会满足一系列条件,这些条件被称为“后条件”或者“后验条件”,比如返回值满足什么关系,某全局变量设置称为什么什么,等等。这应该是函数执行的结果,在前条件满足的情况下,后条件如果没有满足是一种不正常的情况,那么使用异常来处理。
作者:晨池链接:https://www.zhihu.com/question/24461924/answer/27928436来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
-C++中提供了异常类,提供更加灵活的异常处理手段。
二、异常处理的基本流程
-某段程序代码在执行操作时发生了特殊情况,引发一个特定的异常
-另一段程序捕捉这个异常并处理
//异常类:空栈异常类与满栈异常类 class EStackEmpty{};//空类 class EStackFull{}; int JuStack::Pop() { if(IsEmpty())//引发异常 throw EStackEmpty(); //抛出 } void JuStack::Push(int value) { if (IsFull()) throw EStackFull(); }
//异常的捕获 try { 可能引发异常的函数调用 } catch(const 一个异常对象的引用) //捕获抛出的异常,执行相应的处理 { 相应处理 }
三、异常类与异常对象
在实际编程中我们一般需要设计异常类,提供必要的异常信息,以便灵活的处理异常。
//构造异常类 class EStackFull { public; EStackFull(int i):_value(i){} int GetValue(){return _value;} private: int _value; }; void JuStack::Push(int value) { if (IsFull()) throw EStackFull(value);//使用value构造异常类对象并抛出异常 _stk[_top] = value; _top++,_cnt++; }
int main()//对主程序的修改 { JuStack stack(17); try { for (int i=0;i < 32;i++) stack.push(i); //如果发生异常,抛出异常 } catch (const EStackFull & e)//使用异常对象获取详细信息 { std::cerr<<"Stack full when trying to push"<<e.GetValue()<<std::endl; return err_stack_full; //异常提示信息 } return 0; }
四、异常类处理策略
1、异常类可以派生和继承,形成库架构,正常的思维是我们应该有一个顶层的抽象的异常类,然后后续的异常类都应该从这个异常类继承下来,事实上C++标准库已经给我们提供了这样一个异常类,你写的异常类都应该从表中库中的那个异常类继承下来。可捕获的异常对象的形式是需要说明一下的在catch的那个子句中我们可以捕获任何形似的类型,当你以普通形式的形似来捕获的时候,这些异常对象就需要拷贝,你也可以捕获对某形式对象的一个引用,这个时候是没有额外的拷贝动作的,你也可以捕获指向某形式对象的一个指针,不过这时候要求对象动态构造,这样那个指针在catch中才可以访问到。
2、-catch子句中可以封装对这个特定异常的一些必要的处理代码,实际编写程序可以有很多个cache子句,每一个负责捕获一种、一类或者全部异常。
catch(int)、catch(const char*)这一类可以捕获一种异常。catch(const EStackFull &)可以捕获该类或其派生类。catch(...)捕获全部。
在执行的时候所有的catch语句是按照定义的顺序去做的,如果你的异常类有继承层次,那么你要把派生类的catch语句写在前面,把基类的catch语句写在后面,否则派生类异常没有机会执行。
3、-可以在基本任务完成后重新引发所处理的异常
-主要用于在程序终止之前写入日志和实施特殊清除任务
4、栈展开
-异常引发代码和异常处理代码可能属于不同的函数
-当异常发生的时候,沿着异常处理块的嵌套顺序逆向查找能够处理该异常的catch子句
-找到catch子句,处理该异常,异常处理完毕之后,程序保持catch子句所在的函数栈框架,不会返回引发异常的函数栈框架(这就是为什么我们说在异常处理的时候需要按照定义的顺序去做)
-函数栈框架消失了,局部对象被析构,但是如果未执行delete操作,动态分配的目标对象未析构(导致内存泄露)
5、未处理异常
-所有未处理的异常由预定义的sdt::terminate()函数处理
-可以使用std::set_terminate()函数设置std::terminate()函数的处理例程
6、C++11规范中建议使用noexcept代替throw
五、异常描述规范
在编写成员函数时,对于有可能引发异常的函数进行描述,把异常描述写在函数的声明处例如:
public: int Pop() noexcept(false);//会引发异常 int Push() noexcept; //不会引发异常 int A() noexcept(noexcept(expr)); //可能引发异常expr是可以转化为true或者false的常数表达式

浙公网安备 33010602011771号