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的常数表达式   

 

 

 

 

 

posted @ 2017-09-03 15:45  ll-ll  阅读(584)  评论(0)    收藏  举报