C++ Exceptional中有一大段的章节是写异常安全的,这可能也是很多程序员在平时的编程中所容易忽视的一个部分,这里我大体总结一下这本书中所涉及到的有关异常安全的知识。
异常安全有三种层次:
1. 基本的异常安全: 当异常发生时,保证没有内存泄露
2. 强的异常安全:当异常发生时,不但要保证没有内存泄露,还要保证程序的状态和异常发生前是一致的。
3. 异常永不发生的保证: 即保证在任何情况下,异常都不会发生。
在写代码时,尽量保证两个原则:
1. 异常安全,实现以上三种异常安全层次之一。
2. 异常中立(exception neutral),当有异常发生时,捕获后要重新抛出,让调用者知道。
我们讨论一下C++中new操作符和delete操作符的异常安全问题。
new操作符本身是异常安全的,为什么这样说呢,因为如果在new一个或多个对象的时候,一般会发生两种类型的错误。第一种是bad_alloc异常,即没有申请到内存,因为这个时候连内存都没申请到,所以发生了异常,也不会有内存泄露。另外一种情况是,对象的构造发生了异常,这个时候此对象会构造失败,并且已经构造好的对象也会被析构掉,所占用的内存也会被释放掉。所以说new操作符是异常安全的。
对已delete操作符来说,情况就要复杂一些了。在delete一个数组对象时,若其中一个对象发生了异常,将导致在这个对象之后的对象无法被正确析构,从而发生内存泄露。所以说这个就要求我们所提供的类的析构函数是不能抛出异常的。
把有可能产生异常的代码移到另一个函数中。只有当另一个函数里的事情成功做完后,你才可以用不可能抛出异常的操作改变程序的状态
代码示例如下:
template<class T>
class Stack
{
public:
Stack();
~Stack();
Stack(const Stack&);
Stack& operator=(const Stack&);
/*...*/
private:
T* v_; //ptr to a memery area big
size_t vsize_; //enough for 'vsize_' T's
size_t vused_; //of T's acctually in use
}
template<class T>
T* NewCopy( const T* src,
size_t srcsize,
size_t destsize)
{
assert(destsize >= srcsize);
T* dest = new T[destsize];
try
{
copy(src, src+srcsize, dest);
}
catch(...)
{
delete [] dest; // this can't throw
throw; // rethrow original exception,
// exeption neutral pinciple
}
return dest;
}
//copy construction
template<class T>
Stack<T>::Stack(const Stack<T>& other)
:v_(NewCopy(other.v_,
other.vsize_,