作为一个一般性的规则,你应该提供实际可达到的最强力的保证。从异常安全的观点看,不抛出的函数(nothrow functions)是极好的,但是在 C++ 的 C 部分之外部不调用可能抛出异常的函数简直就是寸步难行。使用动态分配内存的任何东西(例如,所有的 STL 容器)假如不能找到足够的内存来满足一个请求,在典型情况下,它就会抛出一个 bad_alloc 异常。只要你能做到就提供不抛出保证,但是对于大多数函数,选择是在基本的保证和强力的保证之间的。
void someFunc() { ... // make copy of local state f1(); f2(); ... // swap modified state into place } 很明显,假如 f1 或 f2 低于强力异常安全,someFunc 就很难成为强力异常安全的。例如,假设 f1 仅提供基本保证。为了让 someFunc 提供强力保证,它必须写代码在调用 f1 之前测定整个程序的状态,并捕捉来自 f1 的所有异常,然后恢复到最初的状态。
即使 f1 和 f2 都是强力异常安全的,事情也好不到哪去。假如 f1 运行完成,程序的状态已经发生了毫无疑问的变化,所以假如随后 f2 抛出一个异常,即使 f2 没有改变任何东西,程序的状态也已经和调用 someFunc 时不同。
问题在于副作用。只要函数仅对局部状态起作用(例如,someFunc 仅仅影响调用它的那个对象的状态),它提供强力保证就相对轻易。当函数的副作用影响了非局部数据,它就会困难得多。例如,假如调用 f1 的副作用是改变数据库,让 someFunc 成为强力异常安全就非常困难。一般情况下,没有办法撤销已经提交的数据库变化,其他数据库客户可能已经看见了数据库的新状态。
请答应我回到怀孕的话题。一个女性或者怀孕或者没有。局部怀孕是绝不可能的。与此相似,一个软件或者是异常安全的或者不是。没有像一个局部异常安全的系统这样的东西。一个系统即使只有一个函数不是异常安全的,那么系统作为一个整体就不是异常安全的,因为调用那个函数可能发生泄漏资源和恶化数据结构。不幸的是,很多 C++ 的遗留代码在写的时候没有留意异常安全,所以现在的很多系统都不是异常安全的。它们混合了用非异常安全(exception-unsafe)的方式书写的代码。