|
如何编写异常安全的C++代码(3) b.或者当对象处于非法状态时,所有操作决不会失败。
在接下来的强保证的讨论中细述如何做到这两点。
强保证是事务性的,这个事务性和数据库的事务性有区别,也有共通性。实现强保证的原则做法是:在可能失败的过程中计算出对象的目标状态,但是不修改对象,在决不失败的过程中,把对象替换到目标状态。考察一个不安全的字符串赋值方法:
string& operator=(const string& rsh){ if (this != &rsh){ myalloc locked_pool(m_data); locked_pool.deallocate(m_data); if (rsh.empty()) m_data = NULL; else{ m_data = locked_pool.allocate(rsh.size() + 1); never_failed_copy(m_data, rsh.m_data, rsh.size() + 1); } } return *this; }
locked_pool是为了锁定内存页。为了讨论的简单起见,我们假设只有locked_pool构造函数和allocate是可能抛出异常的,那么这段代码连基本保证也没有做到。若allocate失败,则m_data取值将是非法的。参考上面的b条目,我们可以这样修改代码:
myalloc locked_pool(m_data); locked_pool.deallocate(m_data); //进入非法状态 m_data = NULL; //立刻再次回到合法状态,且不会失败 if(!rsh.empty()){ m_data = locked_pool.allocate(rsh.size() + 1); never_failed_memcopy(m_data, rsh.m_data, rsh.size() + 1); }
现在,如果locked_pool失败,对象不发生改变。如果allocate失败,对象是一个空字符串,这既不是初始状态,也不是我们预期的目标状态,但它是一个合法状态。我们阐明了实现基本保证所需要的技巧部分,结合前述的基础设施(RAII的运用),完全可以实现基本保证了...哦,其实还是有一点疏漏,不过,那就留到最后吧。
继续,让上面的代码实现强保证:
myalloc locked_pool(m_data); char* tmp = NULL; if(!rsh.empty()){ tmp = locked_pool.allocate(rsh.size() + 1); never_failed_memcopy(tmp, rsh.m_data, rsh.size() + 1); //先生成目标状态 }
|