1. 异常安全的时代意义

当我们在现代C++开发中抛出异常时,就像高速行驶的赛车突然踩下刹车。车辆的姿态控制(资源管理)、乘客的安全(数据完整性)、后续处理流程(业务逻辑)都需要精密设计。异常安全正是构建这种"从容应对突发事件"能力的核心技术。

2003年NASA火星探测器在内存耗尽时依然能保持系统稳定的案例告诉我们:任何关键系统的异常处理都决定了生死存亡。对于每天处理百万级交易的金融系统、实时处理物理计算的游戏引擎,异常安全绝非可有可无的装饰。

2. 异常安全保障等级详解

2.1 无异常安全(No Exception Safety)

// 危险品运输车示例(C++17)
class DangerousTruck {
    int* cargo; // 未受保护的货物
public:
    void loadCargo(int value) {
        delete cargo;          // 先删除旧货物
        cargo = new int(value); // 可能抛出bad_alloc
    }
    
    ~DangerousTruck() { delete cargo; }
};

/*
当执行"new int(value)"时如果内存不足:
1. 旧货物已经被删除(产生空指针)
2. 新货物未能装载
3. 进入"无货物可运输"的异常状态
*/

这种实现如同未装安全气囊的汽车,任何异常都将导致:

  • 内存泄漏(未删除的指针)
  • 数据失效(半成品状态)
  • 不可预测的后续错误

2.2 基本异常安全(Basic Exception Safety)

// 智能保险箱示例(C++17)
class SafeBox {
    std::unique_ptr<std::string> document;
public:
    void storeDocument(std::string content) {
        auto temp = std::make_unique<std::string>(std::move(content)); // 可能抛出
        document.swap(temp); // 原子操作绝不抛出
    }
};

/*
执行策略:
1. 在临时对象中构建新状态
2. 使用无异常操作切换状态
3. 无论成败都确保原始数据有效
*/

如同装配了基础安全装置的保险柜,确保:

  • 无资源泄漏(RAII自动管理)
  • 数据始终有效(要么全旧,要么全新)
  • 业务可继续运行

2.3 强异常安全(Strong Exception Safety)

// 银行交易系统示例(C++17)
class BankTransaction {
    struct State { int from; int to; double amount; };
    std::shared_ptr<State> current; // 不可变状态
    
public:
    void transferMoney(int fromAcc, int toAcc, double amt) {
        auto newState = std::make_shared<State>(State{fromAcc, toAcc, amt});
        if(!validate(*newState)) 
            throw std::logic_error("Invalid transaction");
            
        current = newState; // 原子操作
        executeRealTransfer(*current); // 非异常操作
    }
};

/*
采用"预写入日志"模式:
1. 构建完整的新状态
2. 验证有效性
3. 原子提交操作
4. 保证事务的原子性
*/

这相当于金融级的容灾系统:

  • 全过程要么完全成功
  • 要么完全回退到初始状态
  • 不存在任何中间态

3. 构建稳固系统的四大战法

3.1 RAII资源管理

class DatabaseConnection {
    sqlite3* handle;
public:
    explicit DatabaseConnection(const char* path) {
        if(sqlite3_open(path, &handle) != SQLITE_OK)
            throw std::runtime_error("Open failed");
    }
    
    ~DatabaseConnection() { 
        if(handle) sqlite3_close(handle); 
    }
    
    // 禁用拷贝(保障唯一所有权)
    DatabaseConnection(const DatabaseConnection&) = delete;
    DatabaseConnection& operator=(const DatabaseConnection&) = delete;
};

/*
确保在任意代码路径下:
1. 数据库连接必定关闭
2. 无重复释放风险
3. 异常安全性与作用域自动绑定
*/

3.2 提交回滚模式

class FileSystemEditor {
    std::filesystem::path currentFile;
    std::string backupContent;
    
public:
    void modifyFile(const std::string& newContent) {
        // 阶段1:创建备份
        std::string original = readFile(currentFile);
        
        // 阶段2:尝试写入
        try {
            writeFile(currentFile, newContent); 
        }
        catch(...) {
            // 自动回滚到备份
            writeFile(currentFile, original);
            throw;
        }
        
        // 提交成功则销毁备份
        backupContent.clear();
    }
};

3.3 无异常操作保障

class AtomicInventory {
    struct Data { 
        std::atomic<int> bullets; 
        std::atomic<double> fuel;
    };
    std::shared_ptr<Data> state;
    
public:
    void updateInventory(int b, double f) {
        auto temp = std::make_shared<Data>(*state); // 无异常拷贝
        temp->bullets.store(b, std::memory_order_relaxed);
        temp->fuel.store(f, std::memory_order_relaxed);
        
        state.swap(temp); // 原子指针交换
    }
};

/*
关键技术点:
1. 使用shared_ptr实现写时复制
2. 原子操作保证状态切换的完整性
3. 只读操作无需锁
*/

3.4 STL容器保障实践

以std::vector的push_back操作为例:

void populateData(std::vector<SensorData>& collection) {
    SensorData temp = readSensor();
    
    // 标准库的强力保障
    collection.push_back(temp); 
    /*
    当空间不足时:
    1. 分配新内存(可能失败)
    2. 拷贝元素(可能失败)
    3. 仅当全部成功才销毁旧内存
    4. 始终保持原vector有效
    */
    
    // 标准库承诺:
    // 基本保障:操作失败时保持原状
    // 强保障:当元素类型的拷贝操作不抛出异常时
}

4. 工程实践指南

4.1 应用场景决策树

  • 医疗设备控制 → 强异常安全
  • 游戏引擎渲染 → 基本异常安全
  • 科学计算中间结果 → 无异常安全(性能优先)

4.2 技术选型矩阵

维度 强安全 基本安全 无安全
内存消耗 较高 中等
执行性能 较低 中等
开发成本 中等
调试难度 中等

4.3 异常逃生十二准则

  1. 构造函数失败时,确保不留半成品对象
  2. 析构函数绝对禁止抛出异常
  3. swap操作要实现为无异常操作
  4. 优先使用std::make_shared构建对象
  5. 移动操作应标记为noexcept
  6. 对于可能失败的操作,使用bool返回值替代异常
  7. 关键路径代码使用static_assert验证类型特性
  8. 模板代码中使用noexcept约束类型
  9. 异常发生时避免调用虚函数
  10. 多线程环境使用原子操作保护共享状态
  11. 每个catch块都应当处理或转换异常
  12. 记录异常日志应作为最后防线

5. 深水区生存指南

当遭遇必须使用裸指针的遗留代码时:

class LegacySystemWrapper {
    LegacyHandle handle;
    std::mutex mtx;
    
public:
    void criticalOperation() {
        auto backup = copyLegacyState(handle); // 使用RAII封装
        
        std::lock_guard<std::mutex> lock(mtx);
        try {
            legacy_perform_operation(handle); // 可能崩溃
        }
        catch(...) {
            restoreLegacyState(handle, backup);
            throw;
        }
    }
    
private:
    struct LegacyState { /* ... */ };
    LegacyState copyLegacyState(LegacyHandle h) { /* ... */ }
    void restoreLegacyState(LegacyHandle h, const LegacyState& s) { /* ... */ }
};

6. 通向卓越之路

现代C++提供了异常安全的终极武器库:

template<typename T>
class Transactional {
    T value;
    std::stack<T> history;
    
public:
    template<typename Func>
    void atomicUpdate(Func&& f) {
        history.push(value); // 提交前记录
        try {
            f(value);
            history.pop(); // 提交成功
        }
        catch(...) {
            value = std::move(history.top());
            history.pop();
            throw;
        }
    }
};

// 使用示例
Transactional<Account> acc;
acc.atomicUpdate([](auto& a) {
    a.withdraw(100); // 可能抛出
    a.deposit(100); 
});