前置知识: C++

异常安全

00:00
3 min Intermediate 2026/6/14

异常安全保证与RAII

概述

异常安全(Exception Safety)是指代码在异常发生时仍能保持正确状态的程度。C++ 的异常机制允许错误从深层调用栈传播到合适的处理层,但如果中间代码没有正确处理资源释放和状态一致性,就会导致资源泄漏或数据损坏。异常安全的核心工具是 RAII,它确保无论函数如何退出(正常返回、异常抛出或提前 return),资源都能被正确释放。

基础概念

异常安全等级

等级说明示例
不抛出保证(noexcept)函数保证不抛出异常析构函数、swap 操作
强保证(Strong)操作要么成功,要么状态不变数据库事务
基本保证(Basic)不泄漏资源对象处于有效状态标准库操作
保证异常可能导致资源泄漏或数据损坏应避免

异常安全的三个原则

  1. 资源管理使用 RAII,避免手动 new/delete
  2. 在修改状态完成可能抛出异常的操作
  3. 使用 copy-and-swap用法实现强保证

快速上手

RAII 保证基本异常安全

#include <fstream>
#include <vector>
#include <memory>

// RAII 自动管理资源,即使异常抛出也能正确释放
void processFile(const std::string& path) {
    std::ifstream file(path);  // RAII:文件自动关闭
    std::vector<int> data;     // RAII:内存自动释放
    auto buffer = std::make_unique<char[]>(1024);  // RAII:自动 delete

    // 如果此处抛出异常,file、data、buffer 都会正确释放
    if (!file) throw std::runtime_error("无法打开文件");

    int value;
    while (file >> value) {
        data.push_back(value);  // vector 自动管理内存
    }
    // 函数正常退出或异常退出,所有资源都会被正确释放
}

noexcept 保证

class SafeVector {
    std::vector<int> data_;
public:
    // swap 操作标记为 noexcept
    void swap(SafeVector& other) noexcept {
        data_.swap(other.data_);  // vector::swap 不抛出异常
    }

    // 析构函数隐式 noexcept
    ~SafeVector() = default;

    // push_back 提供强保证
    void push_back(int value) {
        data_.push_back(value);  // 成功则添加,失败则 vector 不变
    }
};

// 全局 swap 使用 noexcept
template<typename T>
void swap(SafeVector<T>& a, SafeVector<T>& b) noexcept {
    a.swap(b);
}

详细用法

copy-and-swap 惯用法

#include <vector>
#include <utility>

class StringTable {
    std::vector<std::string> entries_;
public:
    // 强异常安全的赋值运算符
    StringTable& operator=(StringTable other) noexcept {  // 注意:参数是值传递
        swap(*this, other);  // 交换内容
        return *this;        // other 析构时释放旧数据
    }

    friend void swap(StringTable& a, StringTable& b) noexcept {
        using std::swap;
        swap(a.entries_, b.entries_);
    }

    // 强异常安全的添加操作
    void addEntry(const std::string& entry) {
        std::vector<std::string> new_entries = entries_;  // 先拷贝
        new_entries.push_back(entry);  // 修改副本(可能抛出异常)
        entries_ = std::move(new_entries);  // 不抛出异常的赋值
    }
};

构造函数的异常安全

#include <memory>

class Widget {
    std::unique_ptr<int[]> data_;
    size_t size_;
public:
    // 异常安全的构造函数
    explicit Widget(size_t n) try
        : data_(std::make_unique<int[]>(n))  // 可能抛出 bad_alloc
        , size_(n)
    {
        // 初始化数据
        for (size_t i = 0; i < n; ++i) {
            data_[i] = 0;
        }
    } catch (const std::bad_alloc&) {
        // data_ 尚未构造完成,不需要手动释放
        // unique_ptr 的 RAII 保证不会泄漏
        std::cerr << "内存分配失败" << std::endl;
        throw;  // 重新抛出
    }
};

异常安全与多线程

#include <mutex>
#include <vector>

class ThreadSafeBuffer {
    mutable std::mutex mtx_;
    std::vector<int> buffer_;
public:
    // 异常安全的加锁操作
    void add(int value) {
        std::lock_guard<std::mutex> lock(mtx_);  // RAII 加锁
        buffer_.push_back(value);  // 如果抛出异常,lock 析构时自动解锁
    }

    // 异常安全的批量操作
    void addBatch(const std::vector<int>& values) {
        std::lock_guard<std::mutex> lock(mtx_);
        // 先在副本上操作,确保强保证
        std::vector<int> temp = buffer_;
        for (int v : values) {
            temp.push_back(v);
        }
        buffer_ = std::move(temp);  // noexcept 赋值
    }
};

常见场景

事务性操作

class Account {
    double balance_;
public:
    // 强异常安全的转账操作
    static bool transfer(Account& from, Account& to, double amount) {
        if (from.balance_ < amount) return false;

        // 先扣除,再加增
        // 如果第二步失败,需要回滚第一步
        from.balance_ -= amount;
        try {
            to.balance_ += amount;
        } catch (...) {
            from.balance_ += amount;  // 回滚
            throw;
        }
        return true;
    }
};

资源管理的异常安全

#include <memory>

// 使用 unique_ptr 管理动态数组
class Image {
    std::unique_ptr<unsigned char[]> pixels_;
    int width_, height_;
public:
    Image(int w, int h)
        : pixels_(std::make_unique<unsigned char[]>(w * h * 4))  // RGBA
        , width_(w)
        , height_(h)
    {}

    // 异常安全的像素操作
    void setPixel(int x, int y, unsigned char r, unsigned char g,
                  unsigned char b, unsigned char a) {
        if (x < 0 || x >= width_ || y < 0 || y >= height_) {
            throw std::out_of_range("像素坐标越界");
        }
        int idx = (y * width_ + x) * 4;
        pixels_[idx] = r;
        pixels_[idx + 1] = g;
        pixels_[idx + 2] = b;
        pixels_[idx + 3] = a;
    }
};

注意事项

  • 析构函数绝不能抛出异常,C++11 中析构函数默认为 noexcept
  • 析构函数中如果操作可能失败,应捕获异常并记录日志
  • 移动操作应标记noexcept,否则容器(如 vector)在扩容时会选择拷贝而非移动
  • 不要在构造函数执行可能失败操作后再初始化 RAII 成员,应使用成员初始化列表
  • 标准库容器操作提供的基本保证:不会泄漏内存,对象处于可析构状态

进阶用法

scope_guard 模式

#include <functional>

class ScopeGuard {
    std::function<void()> cleanup_;
    bool active_ = true;
public:
    explicit ScopeGuard(std::function<void()> cleanup)
        : cleanup_(std::move(cleanup)) {}
    ~ScopeGuard() { if (active_) try { cleanup_(); } catch (...) {} }
    void dismiss() { active_ = false; }
    ScopeGuard(const ScopeGuard&) = delete;
    ScopeGuard& operator=(const ScopeGuard&) = delete;
};

// 使用 scope_guard 确保异常安全
void complexOperation() {
    auto* raw = new int[100];
    ScopeGuard guard([&]() { delete[] raw; });  // 确保释放

    // 复杂操作,可能抛出异常
    processRaw(raw);

    guard.dismiss();  // 如果成功,由其他机制管理
}

noexcept 与性能

#include <vector>

class MoveOnlyType {
    int* data_;
    size_t size_;
public:
    // noexcept 移动构造函数使 vector 扩容时使用移动而非拷贝
    MoveOnlyType(MoveOnlyType&& other) noexcept
        : data_(other.data_), size_(other.size_) {
        other.data_ = nullptr;
        other.size_ = 0;
    }

    MoveOnlyType& operator=(MoveOnlyType&& other) noexcept {
        if (this != &other) {
            delete[] data_;
            data_ = other.data_;
            size_ = other.size_;
            other.data_ = nullptr;
            other.size_ = 0;
        }
        return *this;
    }

    ~MoveOnlyType() { delete[] data_; }
};

// vector 扩容时会检查移动构造是否 noexcept
// 如果是,使用移动;否则使用拷贝(更安全但更慢)
std::vector<MoveOnlyType> vec;
vec.push_back(MoveOnlyType(100));  // 使用 noexcept 移动

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式