
1. 异常安全的基本概念在C开发中异常安全Exception Safety是指当程序抛出异常时代码能够保持数据一致性和资源管理的正确性。这个概念最早由David Abrahams在1996年提出后来被Bjarne Stroustrup纳入C标准库设计规范。异常安全的核心在于当异常发生时程序应该处于以下三种状态之一所有对象都处于有效状态没有资源泄漏数据结构保持一致性2. 异常安全的三个等级约定2.1 基本保证Basic Guarantee基本保证是最低级别的异常安全要求它承诺当异常发生时程序不会崩溃所有对象都处于有效状态可能不是预期状态没有资源泄漏实现要点使用RAIIResource Acquisition Is Initialization管理资源在可能抛出异常的操作前保存原始状态确保异常发生时能回滚到有效状态示例代码class DatabaseConnection { Connection* conn; public: void updateRecord(int id, const string data) { auto oldData getRecord(id); // 保存旧状态 conn-beginTransaction(); try { conn-executeUpdate(id, data); conn-commit(); } catch (...) { conn-rollback(); restoreRecord(id, oldData); // 恢复旧状态 throw; } } };2.2 强保证Strong Guarantee强保证比基本保证更严格它承诺操作要么完全成功要么完全失败程序状态与操作前完全一致实现技巧使用copy-and-swap惯用法先完成所有可能失败的操作最后通过不抛异常的操作提交更改典型示例class StringVector { vectorstring data; public: void addStrings(const vectorstring newItems) { auto newData data; // 拷贝构造可能抛出 newData.insert(newData.end(), newItems.begin(), newItems.end()); // 可能抛出 data.swap(newData); // noexcept操作 } };2.3 不抛保证Nothrow Guarantee最高级别的异常安全承诺操作绝对不会抛出异常通常用于析构函数、swap操作等关键函数实现方法仅调用已知不会抛出的操作使用noexcept关键字声明避免内存分配等可能抛出的操作重要应用场景class Buffer { char* ptr; public: ~Buffer() noexcept { delete[] ptr; // delete[]在标准中保证不抛 } void swap(Buffer other) noexcept { std::swap(ptr, other.ptr); } };3. 实现异常安全的关键技术3.1 RAII模式资源获取即初始化RAII是C管理资源的核心理念资源在构造函数中获取在析构函数中释放确保异常发生时资源能被正确释放标准库应用void processFile(const string filename) { ifstream file(filename); // 构造函数打开文件 // 使用文件... // 无论是否抛出异常文件都会在作用域结束时自动关闭 }3.2 Copy-and-Swap惯用法实现强保证的通用技术创建对象的副本在副本上执行修改用swap原子性地替换原对象完整实现示例class Config { mapstring, string settings; public: void updateSettings(const mapstring, string newSettings) { Config temp(*this); // 拷贝构造 for (auto [k,v] : newSettings) { temp.settings[k] v; // 修改副本 } swap(settings, temp.settings); // 原子交换 } };3.3 异常安全函数设计原则最小化可能抛出异常的代码区域确保关键操作如swap不会抛出按从弱到强的顺序处理资源先处理可能失败的操作最后执行不会失败的操作4. 异常安全实践中的常见问题4.1 构造函数中的异常安全构造函数需要特别注意如果构造函数抛出异常析构函数不会被调用必须确保已分配的资源能被正确释放解决方案class ResourceHolder { Resource* res1; Resource* res2; public: ResourceHolder() : res1(new Resource), res2(nullptr) { try { res2 new Resource; } catch (...) { delete res1; // 手动清理 throw; } } ~ResourceHolder() { delete res1; delete res2; } };4.2 多步操作的异常安全对于需要多个步骤的操作使用事务模式或者实现回滚机制事务示例class AccountTransfer { public: void transfer(Account from, Account to, int amount) { auto fromBalance from.getBalance(); from.withdraw(amount); // 可能抛出 try { to.deposit(amount); // 可能抛出 } catch (...) { from.deposit(amount); // 回滚 throw; } } };4.3 标准容器的异常安全STL容器通常提供以下保证插入单个元素强保证或基本保证多个元素插入基本保证erase操作不抛保证swap操作不抛保证使用建议vectorMyClass vec; vec.reserve(100); // 预先分配避免插入时重新分配 try { vec.push_back(MyClass(...)); // 强保证 } catch (...) { // 处理异常 }5. 异常安全与移动语义C11引入的移动语义影响异常安全移动构造函数通常应标记为noexcept标准库对noexcept移动类型有优化最佳实践class Movable { int* data; public: Movable(Movable other) noexcept : data(other.data) { other.data nullptr; } Movable operator(Movable other) noexcept { delete data; data other.data; other.data nullptr; return *this; } };6. 异常安全测试方法验证异常安全的技巧注入异常测试在关键点强制抛出异常状态验证检查异常后的对象状态资源检查确保没有资源泄漏测试示例TEST(ExceptionSafetyTest, StrongGuarantee) { MyClass obj; auto oldState obj.getState(); try { throwAtPoint(3); // 测试时注入异常 obj.modify(); } catch (...) { ASSERT_EQ(obj.getState(), oldState); // 验证状态不变 } ASSERT_NO_LEAKS(); // 检查资源泄漏 }7. 异常安全与并发编程多线程环境下的额外考虑确保异常不会破坏锁的状态使用RAII管理锁避免在持有锁时执行可能抛出的操作锁保护示例class ThreadSafeContainer { mutex mtx; vectorint data; public: void add(int value) { lock_guardmutex lock(mtx); // RAII管理锁 data.push_back(value); // 可能抛出 // 锁会在栈展开时自动释放 } };8. 异常安全性能考量异常安全可能带来的性能影响额外的拷贝操作强保证预留资源如提前分配内存事务开销优化策略class Optimized { vectorExpensive items; public: void add(const Expensive item) { if (items.size() items.capacity()) { vectorExpensive newItems; newItems.reserve(items.capacity() * 2); // 预先分配 newItems items; // 可能抛出 items.swap(newItems); // noexcept } items.push_back(item); // 强保证 } };9. 异常安全设计模式常用设计模式实现异常安全策略模式隔离可能变化的部分命令模式封装操作为对象备忘录模式保存和恢复状态命令模式示例class Command { public: virtual ~Command() default; virtual void execute() 0; virtual void undo() 0; }; class Transaction { vectorunique_ptrCommand commands; public: void add(unique_ptrCommand cmd) { commands.push_back(move(cmd)); } void commit() { vectorunique_ptrCommand executed; try { for (auto cmd : commands) { cmd-execute(); executed.push_back(move(cmd)); } } catch (...) { for (auto it executed.rbegin(); it ! executed.rend(); it) { (*it)-undo(); } throw; } } };10. 现代C中的异常安全改进C17/20对异常安全的增强std::optional避免异常std::variant类型安全访问契约编程C20提案optional使用示例optionalint safeDivide(int a, int b) { if (b 0) return nullopt; return a / b; } void calculate() { if (auto result safeDivide(10, 0)) { use(*result); } else { handleError(); } }