C++异常安全详解
1. 异常安全基本概念
1.1 异常安全级别定义
class DatabaseConnection {
public:
// 无异常安全 - 可能泄漏资源
void unsafe_connect() {
open_connection(); // 可能抛出
authenticate(); // 可能抛出
// 如果authenticate抛出,connection不会关闭
}
// 基本异常安全 - 不泄漏资源,但状态可能改变
void basic_safe_connect() {
open_connection(); // 可能抛出
try {
authenticate(); // 可能抛出
} catch (...) {
close_connection(); // 保证清理
throw;
}
}
// 强异常安全 - 操作要么成功,要么状态不变
void strongly_safe_connect() {
// 创建临时对象,所有操作成功后再提交
TempConnection temp;
temp.open();
temp.authenticate();
temp.commit_to(*this); // 不抛出异常
}
// 不抛异常保证
void close() noexcept {
// 保证不会抛出异常
if (is_connected) {
::close(fd); // 系统调用,不抛异常
is_connected = false;
}
}
};
1.2 异常安全级别总结
| 安全级别 | 描述 | 保证内容 |
|---|---|---|
| 无保证 | 可能发生任何事 | 资源泄漏、数据破坏 |
| 基本保证 | 操作失败时无泄漏 | 无资源泄漏,状态有效但不一定一致 |
| 强保证 | 事务性语义 | 操作成功或状态完全不变 |
| 不抛异常保证 | 绝对安全 | 操作绝不抛出异常 |
2. 常见的异常安全问题
2.1 资源泄漏问题
// 问题示例:原始指针管理
class ResourceUser {
FILE* file;
int* buffer;
public:
void problematic_method(const std::string& filename) {
file = fopen(filename.c_str(), "r"); // 资源1
if (!file) throw std::runtime_error("Cannot open file");
buffer = new int[1000]; // 资源2
if (some_condition()) {
throw std::logic_error("Something wrong"); // 泄漏!
}
process_data(); // 可能抛出
// 如果上面抛出异常,file和buffer都会泄漏
fclose(file);
delete[] buffer;
}
};
2.2 数据结构不一致
class DoubleEntryAccount {
std::vector<Transaction> debit_entries;
std::vector<Transaction> credit_entries;
double balance;
public:
void add_transaction(double amount, const std::string& desc) {
// 问题:部分更新导致不一致
if (amount > 0) {
debit_entries.emplace_back(amount, desc); // 可能抛出bad_alloc
balance += amount;
} else {
credit_entries.emplace_back(-amount, desc); // 可能抛出bad_alloc
balance += amount; // amount为负
}
// 如果emplace_back抛出,balance已更新但条目未添加
}
};
2.3 构造函数中的异常
class ProblematicClass {
std::string name;
std::unique_ptr<int[]> data;
std::thread worker_thread;
public:
ProblematicClass(const std::string& n, size_t size)
: name(n),
data(std::make_unique<int[]>(size)),
worker_thread([this] { background_work(); }) // 最后初始化
{
// 如果thread构造函数抛出,name和data已经构造完成
// 但析构函数不会被调用!
}
// 析构函数需要处理部分构造的状态
~ProblematicClass() {
if (worker_thread.joinable()) {
worker_thread.join();
}
}
};
3. RAII模式 - 核心解决方案
3.1 基本RAII模式
// 文件RAII包装器
class FileRAII {
FILE* file;
public:
explicit FileRAII(const std::string& filename, const char* mode)
: file(fopen(filename.c_str(), mode))
{
if (!file) {
throw std::runtime_error("Cannot open file: " + filename);
}
}
~FileRAII() noexcept {
if (file) {
fclose(file);
}
}
// 禁止拷贝
FileRAII(const FileRAII&) = delete;
FileRAII& operator=(const FileRAII&) = delete;
// 允许移动
FileRAII(FileRAII&& other) noexcept : file(other.file) {
other.file = nullptr;
}
FileRAII& operator=(FileRAII&& other) noexcept {
if (this != &other) {
if (file) fclose(file);
file = other.file;
other.file = nullptr;
}
return *this;
}
FILE* get() const noexcept { return file; }
};
// 使用示例
void safe_file_operation() {
FileRAII file("data.txt", "r"); // 异常安全
read_data(file.get());
// 自动关闭,即使抛出异常
}
3.2 通用RAII包装器
template<typename T, typename Cleanup>
class ScopeGuard {
T resource;
Cleanup cleanup;
bool active;
public:
ScopeGuard(T res, Cleanup clean)
: resource(std::move(res)), cleanup(std::move(clean)), active(true)
{}
~ScopeGuard() {
if (active) {
cleanup(resource);
}
}
// 释放资源所有权
T release() noexcept {
active = false;
return std::move(resource);
}
// 禁用拷贝
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
// 允许移动
ScopeGuard(ScopeGuard&& other) noexcept
: resource(std::move(other.resource)),
cleanup(std::move(other.cleanup)),
active(other.active)
{
other.active = false;
}
};
// 使用示例
void scope_guard_demo() {
int* raw_ptr = new int[100];
auto cleanup = [](int* ptr) { delete[] ptr; };
ScopeGuard guard(raw_ptr, cleanup);
// 使用资源
// 异常安全 - 自动调用cleanup
}
4. 拷贝并交换惯用法(Copy-and-Swap)
4.1 实现强异常安全
class String {
char* data;
size_t length;
// 辅助交换函数
friend void swap(String& first, String& second) noexcept {
using std::swap;
swap(first.data, second.data);
swap(first.length, second.length);
}
public:
// 构造函数
String(const char* str = "") : data(nullptr), length(0) {
if (str) {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
}
// 拷贝构造函数 - 基本异常安全
String(const String& other) : String(other.data) {}
// 移动构造函数 - 不抛异常
String(String&& other) noexcept : data(other.data), length(other.length) {
other.data = nullptr;
other.length = 0;
}
// 析构函数
~String() {
delete[] data;
}
// 拷贝赋值 - 强异常安全使用copy-and-swap
String& operator=(String other) noexcept { // 按值传递!
swap(*this, other);
return *this;
}
// 额外的赋值操作符,同样强异常安全
String& operator=(const char* str) {
String temp(str); // 可能抛出
swap(*this, temp); // 不抛出
return *this;
}
};
4.2 事务性更新模式
class BankAccount {
double balance;
std::string owner;
std::vector<Transaction> history;
public:
// 强异常安全的转账操作
void transfer(double amount, BankAccount& to) {
// 创建所有临时对象
double new_balance = balance - amount;
double to_new_balance = to.balance + amount;
Transaction debit_transaction(-amount, "Transfer out");
Transaction credit_transaction(amount, "Transfer in");
// 验证(可能抛出)
if (new_balance < 0) {
throw std::runtime_error("Insufficient funds");
}
// 提交更改 - 不抛出异常的操作
balance = new_balance;
to.balance = to_new_balance;
history.push_back(std::move(debit_transaction));
to.history.push_back(std::move(credit_transaction));
}
// 另一种方式:使用临时对象
void update_owner(const std::string& new_owner) {
BankAccount temp = *this; // 拷贝
temp.owner = new_owner; // 修改副本
// 如果上面都成功,提交更改
*this = std::move(temp); // 不抛出(移动赋值)
}
};
5. 异常安全的最佳实践
5.1 智能指针的使用
#include <memory>
class SafeResourceManager {
std::unique_ptr<DatabaseConnection> db;
std::shared_ptr<Cache> cache;
std::vector<std::unique_ptr<Handler>> handlers;
public:
// 异常安全的初始化
void initialize() {
// 所有资源在构造失败时自动清理
auto temp_db = std::make_unique<DatabaseConnection>();
auto temp_cache = std::make_shared<Cache>();
temp_db->connect(); // 可能抛出
temp_cache->warmup(); // 可能抛出
// 所有操作成功,提交资源
db = std::move(temp_db);
cache = std::move(temp_cache);
}
// 异常安全的资源获取
std::unique_ptr<Transaction> begin_transaction() {
auto transaction = db->create_transaction(); // 可能抛出
transaction->start(); // 可能抛出
// 如果失败,unique_ptr确保transaction被清理
return transaction;
}
};
5.2 异常安全的容器操作
template<typename T>
class ExceptionSafeVector {
std::vector<T> data;
public:
// 强异常安全的插入
template<typename... Args>
void emplace_back_safe(Args&&... args) {
// 在临时对象中构造新元素
T temp(std::forward<Args>(args)...);
// 预留空间以避免重新分配时的异常
if (data.size() == data.capacity()) {
std::vector<T> new_data;
new_data.reserve(data.capacity() * 2 + 1);
new_data = data; // 拷贝现有数据
// 如果拷贝成功,交换
data.swap(new_data); // 不抛出
}
// 现在可以安全添加
data.push_back(std::move(temp)); // 移动,不抛出(假设T的移动不抛出)
}
// 强异常安全的批量插入
void insert_range_safe(const std::vector<T>& new_items) {
std::vector<T> new_data = data; // 拷贝
// 在副本上进行可能失败的操作
new_data.reserve(data.size() + new_items.size());
for (const auto& item : new_items) {
new_data.push_back(item); // 可能抛出
}
// 成功则提交
data.swap(new_data); // 不抛出
}
};
5.3 异常安全的锁管理
#include <mutex>
class ThreadSafeCounter {
mutable std::mutex mtx;
int value;
public:
// 基本异常安全 - 使用lock_guard
void increment() {
std::lock_guard<std::mutex> lock(mtx); // RAII
++value; // 即使这里抛出,锁也会释放
// 其他可能抛出的操作
}
// 强异常安全的复杂操作
bool transfer_if_positive(int amount, ThreadSafeCounter& other) {
// 同时锁定两个互斥量,避免死锁
std::unique_lock<std::mutex> lock1(mtx, std::defer_lock);
std::unique_lock<std::mutex> lock2(other.mtx, std::defer_lock);
std::lock(lock1, lock2); // RAII保证解锁
// 检查条件
if (value < amount) {
return false;
}
// 创建临时状态
int new_value = value - amount;
int other_new_value = other.value + amount;
// 提交更改
value = new_value;
other.value = other_new_value;
return true;
}
};
6. 高级异常安全技术
6.1 提交/回滚模式
class TransactionalSystem {
struct TransactionState {
int balance;
std::vector<std::string> logs;
// 其他需要保存的状态
};
TransactionState save_state() const {
return {balance, logs}; // 拷贝当前状态
}
void restore_state(const TransactionState& state) noexcept {
balance = state.balance;
logs = state.logs;
}
public:
bool transactional_operation(int param) {
auto saved_state = save_state();
try {
step1(param); // 可能抛出
step2(param); // 可能抛出
step3(param); // 可能抛出
return true; // 成功提交
} catch (...) {
restore_state(saved_state); // 回滚到原始状态
throw;
}
}
};
6.2 异常安全的自定义删除器
// 用于需要复杂清理的资源
template<typename T>
struct CustomDeleter {
void operator()(T* ptr) const noexcept {
try {
if (ptr) {
ptr->cleanup(); // 可能抛出,但被捕获
}
} catch (...) {
// 记录错误,但不能抛出
std::cerr << "Cleanup failed, but continuing..." << std::endl;
}
delete ptr;
}
};
using SafeResource = std::unique_ptr<Resource, CustomDeleter<Resource>>;
SafeResource create_safe_resource() {
auto res = new Resource();
try {
res->initialize(); // 可能抛出
return SafeResource(res);
} catch (...) {
delete res; // 初始化失败,手动删除
throw;
}
}
7. 测试异常安全
7.1 异常安全测试框架
#include <stdexcept>
#include <type_traits>
class ExceptionSafetyTester {
public:
// 测试基本异常安全(无泄漏)
template<typename Func, typename Cleanup>
static bool test_basic_safety(Func operation, Cleanup verify) {
bool leaked = false;
try {
operation(); // 执行可能抛出的操作
} catch (...) {
// 检查资源泄漏
leaked = !verify();
}
return !leaked;
}
// 测试强异常安全(状态不变)
template<typename Obj, typename Func>
static bool test_strong_safety(Obj& object, Func operation) {
auto saved_state = object; // 需要Obj可拷贝
try {
operation();
return true; // 操作成功
} catch (...) {
// 检查状态是否完全恢复
return object == saved_state;
}
}
};
// 使用示例
void test_example() {
SafeResourceManager manager;
// 测试基本安全
bool basic_safe = ExceptionSafetyTester::test_basic_safety(
[&]() { manager.initialize(); },
[&]() { return !manager.has_leaks(); }
);
// 测试强安全
bool strong_safe = ExceptionSafetyTester::test_strong_safety(
manager,
[&]() { manager.complex_operation(); }
);
}
8. 总结
异常安全黄金法则:
- 使用RAII管理所有资源
- 优先使用智能指针而非原始指针
- 对可能失败的操作使用copy-and-swap
- 确保析构函数不抛出异常
- 在修改状态前完成所有可能抛出异常的操作
- 使用事务性语义实现强异常安全
关键实践:
// 好的异常安全实践总结
class WellDesignedClass {
std::unique_ptr<Resource> resource; // RAII管理
std::vector<Data> items;
public:
// 强异常安全的赋值
WellDesignedClass& operator=(WellDesignedClass other) noexcept {
swap(*this, other);
return *this;
}
// 异常安全的方法
void safe_method(const Data& new_item) {
// 准备所有临时对象
auto temp_resource = resource->clone();
std::vector<Data> new_items = items;
// 执行可能失败的操作
temp_resource->process(new_item);
new_items.push_back(new_item);
// 提交更改(不抛出)
resource = std::move(temp_resource);
items.swap(new_items);
}
// 不抛异常的方法
void query() const noexcept {
// 只读操作,保证不抛出
}
};
通过遵循这些原则和实践,可以构建出健壮的、异常安全的C++应用程序。
1420

被折叠的 条评论
为什么被折叠?



