C++中的异常处理机制详解

2020-01-06 17:21:42丽君

上面的代码说明了异常规格说明的基本语法,以及unexpected函数的作用,以及如何自定义自己的unexpected函数,还讨论了在unexpected函数中继续抛出异常的情况下,该如何处理抛出的异常.C++11中取消了这种异常规格说明.引入了一个noexcept函数,用于表明这个函数是否会抛出异常


void recoup(int) noexecpt(true); //recoup不会抛出异常
void recoup(int) noexecpt(false); //recoup可能会抛出异常

此外还提供了noexecpt用来检测一个函数是否不抛出异常.

异常安全

异常安全我觉得是一个挺复杂的点,不光光需要实现函数的功能,还要保存函数不会在抛出异常的情况下,出现不一致的状态.这里举一个例子,大家在实现堆栈的时候经常看到书中的例子都是定义了一个top函数用来获得栈顶元素,还有一个返回值是void的pop函数仅仅只是把栈顶元素弹出,那么为什么没有一个pop函数可以 即弹出栈顶元素,并且还可以获得栈顶元素呢?


template<typename T> T stack<T>::pop()
{
  if(count == 0)
    throw logic_error("stack underflow");
  else
    return data[--count];
}

如果函数在最后一行抛出了一个异常,那么这导致了函数没有将退栈的元素返回,但是Count已经减1了,所以函数希望得到的栈顶元素丢失了.本质原因是因为这个函数试图一次做两件事,1.返回值,2.改变堆栈的状态.最好将这两个独立的动作放到两个独立的函数中,遵守内聚设计的原则,每一个函数只做一件事.我们 再来讨论另外一个异常安全的问题,就是很常见的赋值操作符的写法,如何保证赋值操作是异常安全的.


class Bitmap {...};
class Widget {
  ...
private:
  Bitmap *pb;
};

Widget& Widget::operator=(const Widget& rhs)

{

delete pb;

pb = new Bitmap(*rhs.pb);

return *this;

}

上面的代码不具备自我赋值安全性,倘若rhs就是对象本身,那么将会导致*rhs.pb指向一个被删除了的对象.那么就绪改进下.加入证同性测试.


Widget& Widget::operator=(const Widget& rhs)
{
  If(this == rhs) return *this; //证同性测试
  delete pb;
  pb = new Bitmap(*rhs.pb);
  return *this;
}

但是现在上面的代码依旧不符合异常安全性,因为如果delete pb执行完成后在执行new Bitmap的时候出现了异常,则会导致最终指向一块被删除的内存.现在只要稍微改变一下,就可以让上面的代码具备异常安全性.


Widget& Widget::operator=(const Widget& rhs)
{
  If(this == rhs) return *this; //证同性测试
  Bitmap *pOrig = pb;
  pb = new Bitmap(*rhs.pb); //现在这里即使发生了异常,也不会影响this指向的对象
  delete pOrig;
  return *this;  
}