保证异常安全
7.1 ID_exceptionUnsafe
当产生异常时,保证:
- 相关资源不会泄漏
- 相关对象处于正确状态
是 C++ 异常机制可以正确工作的重要基础。
示例:
void foo() {
lock();
procedure_may_throw(); // Unsafe
unlock();
}
设 lock 是某种获取资源的操作,unlock 是释放资源的操作,procedure_may_throw 是可能抛出异常的过程,那么 foo 函数就不是异常安全的,一旦有异常抛出会导致死锁或泄露等问题。
应保证资源从分配到回收的过程不被异常中断,采用对象化管理方法,使分配和回收得以自动完成:
void foo() {
LockOwner object;
procedure_may_throw(); // Safe
}
将 lock 和 unlock 分别由 object 的构造和析构函数完成,即使 procedure_may_throw 抛出异常,相关资源也可被自动回收,实现了异常安全,资源的对象化管理方法可参见 ID_ownerlessResource。
异常安全的另一个重要方面是抛出异常时应保证相关对象的状态是正确的,事务或算法在处理对象时可能要分多个步骤处理对象的多个成员,要注意中途抛出异常会造成数据不一致等问题。
class X {
T a, b;
public:
void foo() {
proc(a);
// ... if throw an exception ...
proc(b);
}
};
设 a 和 b 是两个密切相关的成员,如账号和金额等,foo 是一个处理事务的函数,如果在中途抛出异常就会使对象处于错误的状态,解决方法可以考虑“复制 - 交换”模式,如:
class X {
T a, b;
public:
void foo() {
X copy(*this);
proc(copy.a);
proc(copy.b);
this->swap(copy);
}
void swap(X& v) noexcept {
....
}
};
事务先处理对象的副本,处理成功后交换副本与对象的数据,交换过程需要保证不抛出异常,这样从对象副本的生成到事务处理完毕的过程中即使抛出异常也不影响对象的状态。
swap 过程不可抛出异常也是一个规则,参见 ID_throwInSwap。
相关
参考
SEI CERT ERR56-CPP
Effective C++ item 29