C++异常安全详解

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. 总结

异常安全黄金法则:

  1. 使用RAII管理所有资源
  2. 优先使用智能指针而非原始指针
  3. 对可能失败的操作使用copy-and-swap
  4. 确保析构函数不抛出异常
  5. 在修改状态前完成所有可能抛出异常的操作
  6. 使用事务性语义实现强异常安全

关键实践:

// 好的异常安全实践总结
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++应用程序。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值