编写异常安全的代码,最困难的地方不在于抛出或捕获异常,而是在抛出和捕获之间我们应该怎么做:必须清理所控制的任何重要资源。
以下为例。
string& string::operator = (const char * str) {
delete [] s_;
if (! str) str = "";
s_ = strcpy( new char [ strlen (str) +1], str);
return *this;
}
尽管c++推荐程序员做到"不在析构中抛出异常",但是new并没有做到这样的承诺。如果在不清楚新缓冲区是否被成功分配前就delete掉原来的缓冲区,可能会使string对象变得糟糕。更好的做法如下:
string& string::operator = (const char * str) {
if (! str) str = "";
char * tmp = strcpy( new char [ strlen (str) +1], str);
delete [] s_;
s_= tmp;
return *this;
}
再举一例。以command模式为例。
void Button::SetAction( const Action * newAction){
Action * tmp = new Action->clone(); //先做再说
delete action_; //然后改变状态
action_ = tmp;
}
如果改成如下样子,就不能处理clone内部的异常了。
那是否使用了try就能解决这个问题呢?看一段新手的代码。void Button::SetAction( const Action * newAction){
delete action_;
action_ = new Action->clone();
}
上面的这段code是异常安全的;Button可以保持稳定的状态,但是它很可能不是原来的那个状态了(action_ = null)。void Button::SetAction( const Action * newAction){
delete action_;
try{
action_ = new Action->clone();
}
catch( ...) {
action_ = null;
throw;
}
}
exceptional c++中很好的总结了这种情形。首先做任何可能抛出异常的事情(但不会改变对象重要的状态),然后以不会抛出异常的操作作为结束。