C++ 异常安全性(Exception Safety):在强一致性事务中实现“不成功则回滚”的逻辑

各位听众,大家好。

今天,我们将深入探讨C++编程中一个至关重要但常被低估的领域:异常安全性(Exception Safety)。尤其是在构建需要强一致性、满足“不成功则回滚”逻辑的系统时,异常安全性不再是锦上添花,而是基石。在复杂的业务逻辑中,一个操作可能涉及多个步骤、修改多个数据结构,如果其中任何一步失败,我们希望整个系统能回滚到操作之前的状态,仿佛什么都没发生过一样。这正是事务(Transaction)的核心理念,也是强异常安全性的最终目标。

我们将以讲座的形式,从C++异常机制的基础讲起,逐步深入到如何设计和实现具备强异常安全性的代码,最终构建一个能够模拟“不成功则回滚”事务行为的系统。

第一讲:异常安全性的基石——C++异常机制回顾

在C++中,异常提供了一种处理运行时错误和异常情况的机制,它允许程序在遇到不可恢复的错误时,将控制权从错误发生点转移到能够处理该错误的代码块。

try, catch, throw 的基本用法

  • throw: 用于抛出一个异常对象。当执行到 throw 语句时,当前函数的执行会被中断,控制权会沿着调用栈向上寻找匹配的 catch 块。
  • try: 包含可能抛出异常的代码。
  • catch: 紧跟在 try 块之后,用于捕获特定类型的异常。
#include <iostream>
#include <stdexcept> // For std::runtime_error

void mightThrowError(int value) {
    if (value < 0) {
        throw std::runtime_error("Negative value not allowed!");
    }
    std::cout << "Value is: " << value << std::endl;
}

int main() {
    try {
        mightThrowError(10);
        mightThrowError(-5); // This will throw an exception
        mightThrowError(20); // This will not be reached
    } catch (const std::runtime_error& e) {
        std::cerr << "Caught runtime error: " << e.what() << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Caught generic exception: " << e.what() << std::endl;
    } catch (...) { // Catch-all handler
        std::cerr << "Caught an unknown exception." << std::endl;
    }
    std::cout << "Program continues after exception handling." << std::endl;
    return 0;
}

栈展开(Stack Unwinding)与 RAII

当异常被抛出时,C++运行时系统会执行一个称为“栈展开”(Stack Unwinding)的过程。它会沿着函数调用栈向上传播异常,途中会销毁所有局部对象。这意味着,在异常传播过程中,所有在栈上创建的对象的析构函数会被调用。

这就是资源获取即初始化(Resource Acquisition Is Initialization, RAII)模式发挥关键作用的地方。RAII是一种C++编程范式,它将资源的生命周期绑定到对象的生命周期。资源(如内存、文件句柄、网络连接、锁等)在对象构造时获取,并在对象析构时释放。

利用RAII,即使在异常发生导致函数提前退出时,资源也能被正确释放,从而避免资源泄露。

#include <iostream>
#include <fstream>
#include <memory> // For std::unique_ptr
#include <mutex>  // For std::mutex, std::lock_guard

// Custom RAII guard for a file handle
class FileGuard {
public:
    FileGuard(const std::string& filename) : file_(filename, std::ios::out) {
        if (!file_.is_open()) {
            throw std::runtime_error("Failed to open file: " + filename);
        }
        std::cout << "File '" << filename << "' opened." << std::endl;
    }

    ~FileGuard() {
        if (file_.is_open()) {
            file_.close();
            std::cout << "File closed." << std::endl;
        }
    }

    std::ofstream& getFile() { return file_; }

private:
    std::ofstream file_;
};

// Custom RAII guard for a mutex lock
class LockGuard {
public:
    LockGuard(std::mutex& mtx) : mtx_(mtx) {
        mtx_.lock();
        std::cout << "Mutex locked." << std::endl;
    }
    ~LockGuard() {
        mtx_.unlock();
        std::cout << "Mutex unlocked." << std::endl;
    }
private:
    std::mutex& mtx_;
};

std::mutex global_mutex;

void processData(int value) {
    LockGuard lock(global_mutex); // Lock acquired
    FileGuard fg("output.txt");   // File opened

    fg.getFile() << "Processing value: " << value << std::endl;

    if (value < 0) {
        throw std::runtime_error("Invalid data for processing.");
    }

    // ... further processing ...
    std::cout << "Data processed successfully." << std::endl;
    // Lock and FileGuard will be destructed automatically
    // when function exits (either normally or via exception).
}

int main() {
    try {
        processData(10);
        std::cout << "---" << std::endl;
        processData(-5); // This will throw
    } catch (const std::runtime_error& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }
    std::cout << "Main program ends." << std::endl;
    return 0;
}

在这个例子中,无论是 processData 函数正常完成,还是因为异常提前退出,LockGuardFileGuard 对象的析构函数都会被调用,从而确保锁被释放,文件被关闭。这是实现异常安全性的基石。

noexcept 说明符

noexcept 说明符用于向编译器承诺一个函数不会抛出异常。如果一个函数声明为 noexcept 但实际上抛出了异常,程序会调用 std::terminate 并立即终止,而不是进行栈展开。

  • noexcept: 函数不抛出任何异常。
  • noexcept(true): 同 noexcept
  • noexcept(false): 函数可能会抛出异常(默认行为)。

使用 noexcept 可以帮助编译器进行优化,并允许标准库容器(如 std::vector)在某些操作(如重新分配内存)中提供更强的异常保证。但是,滥用 noexcept 而不遵守其承诺,会导致程序非预期终止,因此必须谨慎使用。它通常用于移动构造函数、移动赋值运算符、析构函数以及一些不会失败的工具函数。

第二讲:理解异常安全等级——从基础到强大

异常安全性并非非黑即白,它有不同的等级。理解这些等级对于设计健壮的系统至关重要。通常,我们讨论三种主要的异常安全保证:

1. 无抛出保证(Nothrow Guarantee)

  • 定义:操作永远不会抛出异常。如果它真的抛出,程序会调用 std::terminate
  • 特点:这是最强的保证。通常适用于基本类型操作、析构函数(关键!)以及一些纯粹的查询函数。
  • 实现:通常通过将函数标记为 noexcept 来声明。
  • 示例
    void swap(int& a, int& b) noexcept {
        int temp = a;
        a = b;
        b = temp;
    }

2. 强异常安全保证(Strong Exception Safety Guarantee)

  • 定义:如果操作成功,它将完成所有预期的效果。如果操作失败(抛出异常),系统将回滚到操作开始之前的状态,仿佛什么都没有发生过一样。没有资源泄露,也没有副作用。
  • 特点:实现了“不成功则回滚”的原子性。对外部可见的状态要么完全改变,要么完全不变。
  • 实现:通常通过Copy-and-Swap idiom、事务性对象或日志机制来实现。这是我们今天讲座的重点。
  • 示例
    在一个 std::vector 中插入元素。如果内存分配失败,vector 应该保持原样,不应该出现部分插入或损坏的状态。

3. 基本异常安全保证(Basic Exception Safety Guarantee)

  • 定义:如果操作失败(抛出异常),系统将处于有效但可能不确定的状态。没有资源泄露,但数据可能已部分修改或丢失,不再是操作前的状态,也可能不是完全成功的状态。
  • 特点:保证资源不泄露,对象处于可用的状态,但具体内容或值可能无法预测。这是许多C++标准库操作的最低保证。
  • 实现:确保所有资源都通过RAII管理,并在异常发生时正确释放。
  • 示例
    向文件写入数据。如果写入过程中发生错误,文件可能只写入了一部分,但文件句柄最终会关闭。

异常安全等级对比

保证类型 描述 状态回滚? 资源泄露? 数据有效性? 复杂度 典型场景
无抛出 永远不抛出异常。如果抛出则程序终止。 是(因为无失败) 是(因为无失败) 最低 析构函数,移动操作,纯计算函数,低层原子操作
强异常安全 如果操作失败,系统状态恢复到操作开始之前。 是(回滚到旧状态) 事务性操作,修改共享状态的复杂函数,Copy-and-Swap Idiom
基本异常安全 如果操作失败,系统处于有效但可能不确定的状态,无资源泄露。 是(但内容可能不确定) 中等 大多数标准库容器操作,任何修改数据且无法完全回滚的操作

第三讲:实现强异常安全的关键策略

实现强异常安全性,特别是“不成功则回滚”的逻辑,需要精心设计。以下是一些核心策略。

1. Copy-and-Swap Idiom(复制并交换)

Copy-and-Swap 是实现类赋值运算符和强异常安全性的强大模式。其核心思想是:在一个副本上执行所有可能抛出异常的操作,如果所有操作都成功,再将副本的状态与原对象的状态进行原子交换。如果中间有任何操作抛出异常,副本会被销毁,原对象不受影响。

步骤:

  1. 创建一个当前对象的副本。
  2. 对副本执行所有修改操作。这些操作可能抛出异常。
  3. 如果所有修改成功,将当前对象与副本的数据进行交换。这个交换操作必须是noexcept的。
  4. 副本被销毁,其析构函数会处理旧数据。

示例:实现一个简单的动态数组 MyVector

#include <algorithm> // For std::swap
#include <stdexcept>
#include <iostream>
#include <vector> // For comparison

template <typename T>
class MyVector {
public:
    // 默认构造函数
    MyVector() : data_(nullptr), size_(0), capacity_(0) {}

    // 构造函数
    explicit MyVector(size_t initial_capacity) : size_(0) {
        if (initial_capacity > 0) {
            data_ = new T[initial_capacity];
            capacity_ = initial_capacity;
        } else {
            data_ = nullptr;
            capacity_ = 0;
        }
    }

    // 析构函数
    ~MyVector() {
        delete[] data_;
    }

    // 拷贝构造函数 (确保强异常安全)
    MyVector(const MyVector& other) : size_(other.size_), capacity_(other.capacity_) {
        if (other.capacity_ > 0) {
            data_ = new T[other.capacity_]; // 可能抛出 std::bad_alloc
            for (size_t i = 0; i < other.size_; ++i) {
                data_[i] = other.data_[i]; // 可能抛出 T 的拷贝构造函数异常
            }
        } else {
            data_ = nullptr;
        }
    }

    // 移动构造函数 (通常应为 noexcept)
    MyVector(MyVector&& other) noexcept
        : data_(other.data_), size_(other.size_), capacity_(other.capacity_) {
        other.data_ = nullptr;
        other.size_ = 0;
        other.capacity_ = 0;
    }

    // 核心:swap 函数 (noexcept)
    void swap(MyVector& other) noexcept {
        using std::swap; // 启用ADL
        swap(data_, other.data_);
        swap(size_, other.size_);
        swap(capacity_, other.capacity_);
    }

    // 拷贝赋值运算符 (使用 Copy-and-Swap Idiom 实现强异常安全)
    MyVector& operator=(MyVector other) noexcept { // 注意:参数是按值传递,利用了拷贝构造函数
        swap(other); // 将当前对象与副本交换
        return *this;
    }

    // 访问元素
    T& operator[](size_t index) {
        if (index >= size_) {
            throw std::out_of_range("Index out of bounds");
        }
        return data_[index];
    }

    const T& operator[](size_t index) const {
        if (index >= size_) {
            throw std::out_of_range("Index out of bounds");
        }
        return data_[index];
    }

    size_t size() const { return size_; }
    size_t capacity() const { return capacity_; }

    // 扩容函数 (可能抛出异常)
    void reserve(size_t new_capacity) {
        if (new_capacity <= capacity_) {
            return;
        }

        T* new_data = new T[new_capacity]; // 可能抛出 std::bad_alloc
        for (size_t i = 0; i < size_; ++i) {
            new_data[i] = data_[i]; // 可能抛出 T 的拷贝构造函数/赋值运算符异常
        }

        delete[] data_; // 只有在所有操作成功后才释放旧资源
        data_ = new_data;
        capacity_ = new_capacity;
    }

    // 添加元素 (实现强异常安全)
    void push_back(const T& value) {
        if (size_ == capacity_) {
            // 需要扩容
            size_t new_capacity = (capacity_ == 0) ? 1 : capacity_ * 2;
            // 这里的 reserve 内部也需要注意异常安全
            // 如果 reserve 失败,当前 MyVector 对象应保持不变
            // 最简单的方式是让 reserve 内部完成 Copy-and-Swap 逻辑
            // 但为了简化,我们假设 reserve 已经提供了强异常安全或者我们在这里处理

            // 为了保持 push_back 的强异常安全,我们可以在这里使用临时 MyVector
            MyVector temp_vector(*this); // 拷贝构造可能抛异常
            temp_vector.reserve(new_capacity); // reserve内部也必须是强异常安全的
            temp_vector.push_back_internal(value); // 临时添加
            swap(temp_vector); // 交换成功后,temp_vector 的析构函数会处理旧数据
        } else {
            push_back_internal(value);
        }
    }

    // 内部帮助函数,不进行容量检查,假定有足够空间
    void push_back_internal(const T& value) {
        data_[size_] = value; // 可能抛出 T 的拷贝赋值运算符异常
        size_++;
    }

private:
    T* data_;
    size_t size_;
    size_t capacity_;
};

int main() {
    MyVector<int> vec;
    std::cout << "Initial: size=" << vec.size() << ", capacity=" << vec.capacity() << std::endl;

    try {
        vec.push_back(10);
        vec.push_back(20);
        std::cout << "After 2 pushes: size=" << vec.size() << ", capacity=" << vec.capacity() << std::endl;
        std::cout << "Elements: " << vec[0] << ", " << vec[1] << std::endl;

        MyVector<int> vec2;
        vec2 = vec; // 使用 Copy-and-Swap 赋值
        std::cout << "vec2 after assignment: size=" << vec2.size() << ", capacity=" << vec2.capacity() << std::endl;

        // 模拟一个失败的 push_back (例如,如果是自定义类型,其拷贝构造函数可能失败)
        // 这里我们通过手动设置容量限制来模拟内存分配失败的场景
        // 实际上,std::bad_alloc 可能会在 new T[new_capacity] 时发生
        // 为了演示,假设 MyVector<ThrowingType> 会在拷贝时抛出

        // 假设 T 是一个在特定条件下会抛出异常的类型
        class ThrowingType {
        public:
            int id;
            ThrowingType(int i) : id(i) {
                // std::cout << "ThrowingType(" << id << ") constructed." << std::endl;
            }
            ThrowingType(const ThrowingType& other) : id(other.id) {
                // std::cout << "ThrowingType(" << id << ") copied." << std::endl;
                if (id == 99) { // 模拟特定值导致拷贝失败
                    throw std::runtime_error("Copy of 99 failed!");
                }
            }
            ThrowingType& operator=(const ThrowingType& other) {
                // std::cout << "ThrowingType(" << id << ") assigned from " << other.id << "." << std::endl;
                if (other.id == 99) { // 模拟特定值导致赋值失败
                    throw std::runtime_error("Assignment of 99 failed!");
                }
                id = other.id;
                return *this;
            }
            ~ThrowingType() {
                // std::cout << "ThrowingType(" << id << ") destructed." << std::endl;
            }
        };

        MyVector<ThrowingType> throwing_vec;
        throwing_vec.push_back(ThrowingType(1));
        throwing_vec.push_back(ThrowingType(2));
        std::cout << "throwing_vec current size: " << throwing_vec.size() << std::endl;

        try {
            throwing_vec.push_back(ThrowingType(99)); // This will cause a copy error during resize/internal push
        } catch (const std::runtime_error& e) {
            std::cerr << "Caught expected error: " << e.what() << std::endl;
            // 验证 throwing_vec 的状态是否未变
            std::cout << "throwing_vec after failed push: size=" << throwing_vec.size() << ", capacity=" << throwing_vec.capacity() << std::endl;
            std::cout << "Elements: " << throwing_vec[0].id << ", " << throwing_vec[1].id << std::endl;
        }

    } catch (const std::exception& e) {
        std::cerr << "Unexpected error: " << e.what() << std::endl;
    }

    return 0;
}

MyVectorpush_backoperator= 中,我们利用了 Copy-and-Swap。当 push_back 需要扩容时,它会创建一个临时的 MyVector 对象,在这个临时对象上执行扩容和添加新元素的操作。如果这些操作失败,临时对象会被销毁,vec 保持不变。如果所有操作成功,vec 与临时对象交换数据,从而实现强异常安全。

2. RAII for Resource Management(资源管理中的RAII)

RAII不仅用于内存管理(如 std::unique_ptrstd::shared_ptr),更是管理所有类型资源的基石,包括文件句柄、数据库连接、互斥锁等。

#include <fstream>
#include <mutex>
#include <stdexcept>
#include <vector>
#include <iostream>

// 这是一个通用的 RAII 守卫,用于在构造时执行一个动作,在析构时执行另一个动作
class ScopeGuard {
public:
    template<typename EnterFunc, typename ExitFunc>
    ScopeGuard(EnterFunc enter, ExitFunc exit) : exit_func_(exit) {
        enter();
    }
    ~ScopeGuard() {
        exit_func_();
    }
private:
    std::function<void()> exit_func_;
};

// 数据库连接模拟
class DatabaseConnection {
public:
    DatabaseConnection(const std::string& conn_str) : connected_(false) {
        std::cout << "Attempting to connect to DB: " << conn_str << std::endl;
        // 模拟连接失败
        if (conn_str == "bad_connection") {
            throw std::runtime_error("Failed to connect to database.");
        }
        connected_ = true;
        std::cout << "DB connected." << std::endl;
    }
    ~DatabaseConnection() {
        if (connected_) {
            std::cout << "DB disconnected." << std::endl;
        }
    }
    void executeQuery(const std::string& query) {
        if (!connected_) {
            throw std::runtime_error("Not connected to database.");
        }
        std::cout << "Executing query: " << query << std::endl;
        // 模拟查询失败
        if (query.find("FAIL") != std::string::npos) {
            throw std::runtime_error("Query execution failed!");
        }
    }
private:
    bool connected_;
};

void performDbOperation(const std::string& conn_str, const std::vector<std::string>& queries) {
    // 使用 RAII 管理数据库连接
    // 如果 DatabaseConnection 构造失败,下面的代码不会执行,也不会有资源泄露
    DatabaseConnection db(conn_str); 

    // 假设 db 支持事务,这里我们只是模拟连接和查询
    // 真正的事务处理将更复杂,见下一讲

    for (const auto& query : queries) {
        db.executeQuery(query); // 每次查询都可能抛出异常
    }
    std::cout << "All queries executed successfully." << std::endl;
}

int main() {
    std::vector<std::string> good_queries = {"SELECT * FROM users", "INSERT INTO logs VALUES ('OK')"};
    std::vector<std::string> bad_queries_1 = {"SELECT * FROM users", "INSERT FAIL INTO logs"}; // Query fails
    std::vector<std::string> bad_queries_2 = {"SELECT * FROM users", "UPDATE users SET name='John' WHERE id=1"};

    try {
        std::cout << "n--- Test 1: Successful operations ---" << std::endl;
        performDbOperation("good_connection", good_queries);
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }

    try {
        std::cout << "n--- Test 2: Connection failure ---" << std::endl;
        performDbOperation("bad_connection", good_queries);
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }

    try {
        std::cout << "n--- Test 3: Query failure ---" << std::endl;
        performDbOperation("good_connection", bad_queries_1);
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }

    std::cout << "nProgram finished." << std::endl;
    return 0;
}

在这个例子中,DatabaseConnection 的构造函数和析构函数确保了连接的正确建立和关闭,即使在 executeQuery 抛出异常时也能正常工作。这满足了基本异常安全。

3. 状态管理与回滚点

对于更复杂的事务,仅仅依靠Copy-and-Swap可能不够。当一个操作涉及修改多个独立的对象或数据结构时,我们需要一个更全面的机制来记录所有变更,并在失败时进行回滚。

这通常涉及以下技术:

  • 临时副本(Temporary Copies):在操作开始前创建所有相关数据的副本。如果操作成功,用副本替换原数据;如果失败,丢弃副本。
  • 日志/撤销栈(Log/Undo Stack):记录所有修改操作及其逆操作。如果操作成功,清除日志;如果失败,按逆序执行日志中的撤销操作。
  • Memento模式(备忘录模式):在操作开始前保存对象的内部状态(备忘录),操作失败时恢复备忘录。

这些技术是构建下一讲“事务逻辑”的基础。

第四讲:构建“不成功则回滚”的事务逻辑

现在,我们将结合之前学到的知识,设计并实现一个具备强异常安全性的“事务”机制。我们将以一个简单的内存键值存储(Key-Value Store)为例,为其添加事务支持,确保修改要么完全提交,要么完全回滚。

事务的生命周期

一个典型的事务有以下生命周期:

  1. Begin Transaction:开始一个事务,记录当前系统状态或准备记录后续变更。
  2. Perform Operations:在事务内部执行一系列数据修改操作。
  3. Commit Transaction:如果所有操作都成功,将事务中的所有变更原子性地应用到系统。
  4. Rollback Transaction:如果任何操作失败或显式回滚,撤销事务中所有未提交的变更,恢复到事务开始前的状态。

事务管理器模式(Transaction Manager Pattern)

我们将创建一个 TransactionalKeyValueStore 类,并引入一个 Transaction 类或一个 TransactionScope RAII类来管理事务。

设计思路:

  • 主存储区std::map<std::string, std::string> 存储最终的键值对。
  • 事务暂存区:每个正在进行的事务会有一个独立的 std::map<std::string, std::string> 来存储其暂时的修改。
  • 删除标记:事务中删除的键需要特殊标记,以区分“不存在”和“在事务中被删除”。
  • 事务栈:支持嵌套事务,需要一个栈来管理当前的活动事务。
  • RAII 事务守卫:使用一个RAII类 TransactionGuard 来自动处理事务的提交或回滚。
#include <iostream>
#include <map>
#include <string>
#include <vector>
#include <stdexcept>
#include <algorithm> // For std::for_each
#include <functional> // For std::function

// 表示一个键被删除的特殊值
const std::string DELETED_MARK = "[[__DELETED__]]";

// 前向声明
class TransactionalKeyValueStore;

// 事务作用域守卫类
class TransactionGuard {
public:
    TransactionGuard(TransactionalKeyValueStore& store);
    ~TransactionGuard();

    void commit(); // 显式提交事务
    void rollback(); // 显式回滚事务

private:
    TransactionalKeyValueStore& store_;
    bool committed_; // 标记事务是否已提交
};

// 事务性键值存储
class TransactionalKeyValueStore {
public:
    // 构造函数
    TransactionalKeyValueStore() = default;

    // 获取值 (读取总是从当前活动事务的视图开始,向上查找)
    std::string get(const std::string& key) const {
        // 从当前最内层事务的暂存区开始查找
        for (auto it = transactions_.rbegin(); it != transactions_.rend(); ++it) {
            auto& transaction_map = *it;
            auto find_it = transaction_map.find(key);
            if (find_it != transaction_map.end()) {
                if (find_it->second == DELETED_MARK) {
                    throw std::out_of_range("Key not found (deleted in active transaction)");
                }
                return find_it->second;
            }
        }
        // 如果事务栈中没有,则从主存储区查找
        auto find_it = main_store_.find(key);
        if (find_it == main_store_.end()) {
            throw std::out_of_range("Key not found");
        }
        return find_it->second;
    }

    // 设置值 (总是在当前活动事务的暂存区中修改)
    void set(const std::string& key, const std::string& value) {
        if (transactions_.empty()) {
            // 没有活动事务,直接修改主存储区 (这本身不是事务性的,但允许操作)
            // 在实际系统中,通常会强制所有修改都在事务中进行
            main_store_[key] = value;
            std::cout << "[WARN] Set '" << key << "' directly to main store (no active transaction)." << std::endl;
        } else {
            transactions_.back()[key] = value;
            std::cout << "Transaction " << transactions_.size() << ": Set '" << key << "' to '" << value << "'" << std::endl;
        }
    }

    // 删除值 (在当前活动事务的暂存区中标记为删除)
    void remove(const std::string& key) {
        if (transactions_.empty()) {
            main_store_.erase(key);
            std::cout << "[WARN] Removed '" << key << "' directly from main store (no active transaction)." << std::endl;
        } else {
            transactions_.back()[key] = DELETED_MARK;
            std::cout << "Transaction " << transactions_.size() << ": Marked '" << key << "' for deletion." << std::endl;
        }
    }

    // 开始一个新事务
    void begin_transaction() {
        transactions_.emplace_back(); // 压入一个新的空映射作为当前事务的暂存区
        std::cout << "Transaction " << transactions_.size() << ": Started." << std::endl;
    }

    // 提交当前事务
    void commit_transaction() {
        if (transactions_.empty()) {
            throw std::runtime_error("No active transaction to commit.");
        }

        std::map<std::string, std::string> current_transaction_changes = transactions_.back();
        transactions_.pop_back(); // 弹出当前事务

        // 将当前事务的变更应用到其父事务或主存储区
        if (!transactions_.empty()) {
            // 如果有父事务,将变更合并到父事务的暂存区
            std::map<std::string, std::string>& parent_transaction_map = transactions_.back();
            for (const auto& pair : current_transaction_changes) {
                parent_transaction_map[pair.first] = pair.second;
            }
            std::cout << "Transaction " << transactions_.size() + 1 << ": Committed to parent transaction " << transactions_.size() << "." << std::endl;
        } else {
            // 如果没有父事务,将变更应用到主存储区
            for (const auto& pair : current_transaction_changes) {
                if (pair.second == DELETED_MARK) {
                    main_store_.erase(pair.first);
                } else {
                    main_store_[pair.first] = pair.second;
                }
            }
            std::cout << "Transaction " << transactions_.size() + 1 << ": Committed to main store." << std::endl;
        }
    }

    // 回滚当前事务
    void rollback_transaction() {
        if (transactions_.empty()) {
            throw std::runtime_error("No active transaction to rollback.");
        }
        transactions_.pop_back(); // 简单地丢弃当前事务的暂存区
        std::cout << "Transaction " << transactions_.size() + 1 << ": Rolled back." << std::endl;
    }

    // 打印当前主存储区状态
    void print_main_store() const {
        std::cout << "--- Main Store State ---" << std::endl;
        if (main_store_.empty()) {
            std::cout << "(Empty)" << std::endl;
        } else {
            for (const auto& pair : main_store_) {
                std::cout << pair.first << ": " << pair.second << std::endl;
            }
        }
        std::cout << "------------------------" << std::endl;
    }

    // 打印当前事务栈中的状态 (调试用)
    void print_transaction_stack() const {
        std::cout << "--- Transaction Stack State (" << transactions_.size() << " active) ---" << std::endl;
        if (transactions_.empty()) {
            std::cout << "(Empty)" << std::endl;
        } else {
            int i = 0;
            for (const auto& tx_map : transactions_) {
                std::cout << "  Transaction " << ++i << ":" << std::endl;
                if (tx_map.empty()) {
                    std::cout << "    (Empty changes)" << std::endl;
                } else {
                    for (const auto& pair : tx_map) {
                        std::cout << "    " << pair.first << ": " << (pair.second == DELETED_MARK ? "[DELETED]" : pair.second) << std::endl;
                    }
                }
            }
        }
        std::cout << "------------------------" << std::endl;
    }

private:
    std::map<std::string, std::string> main_store_;
    std::vector<std::map<std::string, std::string>> transactions_; // 事务栈,每个元素是一个事务的暂存区
};

// TransactionGuard 实现
TransactionGuard::TransactionGuard(TransactionalKeyValueStore& store)
    : store_(store), committed_(false) {
    store_.begin_transaction();
}

TransactionGuard::~TransactionGuard() {
    if (!committed_) {
        store_.rollback_transaction();
    }
}

void TransactionGuard::commit() {
    store_.commit_transaction();
    committed_ = true;
}

void TransactionGuard::rollback() {
    store_.rollback_transaction();
    committed_ = true; // 标记已处理,析构函数不再回滚
}

// 模拟一个可能失败的操作
void do_complex_update(TransactionalKeyValueStore& store, const std::string& key, const std::string& value, bool should_fail) {
    std::cout << "nAttempting complex update for key '" << key << "'..." << std::endl;
    store.set(key, value + "_temp"); // 事务内临时修改

    if (should_fail) {
        std::cout << "  Simulating failure!" << std::endl;
        throw std::runtime_error("Simulated failure during complex update.");
    }

    store.set(key, value); // 最终修改
    store.set("log_entry", "Updated " + key); // 另一个修改
    std::cout << "  Complex update completed successfully." << std::endl;
}

int main() {
    TransactionalKeyValueStore store;
    store.print_main_store();

    // --- 场景 1: 成功提交一个事务 ---
    std::cout << "n--- Scenario 1: Successful Transaction ---" << std::endl;
    try {
        TransactionGuard tx_guard(store); // 开始事务
        store.set("user_id", "123");
        store.set("username", "Alice");
        do_complex_update(store, "email", "[email protected]", false); // 成功操作
        tx_guard.commit(); // 提交事务
    } catch (const std::exception& e) {
        std::cerr << "Caught error: " << e.what() << std::endl;
    }
    store.print_main_store(); // 应该看到所有变更

    // --- 场景 2: 事务因异常回滚 ---
    std::cout << "n--- Scenario 2: Transaction Rolls Back on Exception ---" << std::endl;
    try {
        TransactionGuard tx_guard(store); // 开始事务
        store.set("user_id", "456"); // 修改 user_id
        store.set("username", "Bob");
        do_complex_update(store, "address", "123 Main St", true); // 模拟失败,抛出异常
        tx_guard.commit(); // 不会执行到这里
    } catch (const std::exception& e) {
        std::cerr << "Caught expected error during transaction: " << e.what() << std::endl;
    }
    store.print_main_store(); // user_id 和 username 应该保持为 Alice 和 123,address 应该不存在

    // --- 场景 3: 嵌套事务 (外部成功,内部失败) ---
    std::cout << "n--- Scenario 3: Nested Transactions (Outer success, Inner failure) ---" << std::endl;
    try {
        TransactionGuard outer_tx(store); // 外部事务
        store.set("city", "New York");

        try {
            TransactionGuard inner_tx(store); // 内部事务
            store.set("zip_code", "10001");
            do_complex_update(store, "state", "NY", true); // 内部事务失败
            inner_tx.commit();
        } catch (const std::exception& e) {
            std::cerr << "  Caught error in inner transaction: " << e.what() << std::endl;
            // 内部事务被回滚,但外部事务仍然活跃
        }

        // 外部事务继续,可以看到 inner_tx 失败后对 state 和 zip_code 的修改都回滚了
        // store.get("zip_code") 此时会抛出 key not found,因为 internal_tx 回滚了
        // store.get("state") 也会抛出 key not found
        store.set("country", "USA"); // 外部事务继续修改
        outer_tx.commit(); // 提交外部事务
    } catch (const std::exception& e) {
        std::cerr << "Caught error in outer transaction: " << e.what() << std::endl;
    }
    store.print_main_store(); // 应该看到 city 和 country,zip_code 和 state 不应该存在

    // --- 场景 4: 嵌套事务 (外部失败) ---
    std::cout << "n--- Scenario 4: Nested Transactions (Outer failure) ---" << std::endl;
    try {
        TransactionGuard outer_tx(store); // 外部事务
        store.set("temp_key_outer", "temp_value_outer");

        try {
            TransactionGuard inner_tx(store); // 内部事务
            store.set("temp_key_inner", "temp_value_inner");
            inner_tx.commit(); // 内部事务成功提交到外部事务
            std::cout << "  Inner transaction committed to outer." << std::endl;
        } catch (const std::exception& e) {
            std::cerr << "  Caught error in inner transaction: " << e.what() << std::endl;
        }

        // 此时,temp_key_inner 已经合并到了 outer_tx 的暂存区
        std::cout << "  Outer transaction about to fail..." << std::endl;
        throw std::runtime_error("Simulated outer transaction failure."); // 外部事务失败

        outer_tx.commit();
    } catch (const std::exception& e) {
        std::cerr << "Caught expected error during outer transaction: " << e.what() << std::endl;
    }
    store.print_main_store(); // 所有修改都应该回滚

    return 0;
}

在这个键值存储的例子中:

  • TransactionalKeyValueStore 管理了主数据 (main_store_) 和一个事务栈 (transactions_)。
  • setremove 操作都在当前活动事务的暂存区 (transactions_.back()) 进行。
  • get 操作会从内层事务开始,逐级向外查找,最后查找主存储区,以提供事务隔离视图。
  • begin_transaction 压入一个新的空 map 到事务栈。
  • commit_transaction 将当前事务的变更合并到其父事务或主存储区,并弹出当前事务。
  • rollback_transaction 简单地丢弃当前事务的暂存区,并弹出当前事务。
  • TransactionGuard RAII 类确保了无论代码块如何退出(正常退出或抛出异常),事务都会被正确地提交或回滚。这是实现“不成功则回滚”的关键。

这个设计有效地利用了RAII和临时状态管理,实现了强异常安全性和事务的原子性。

第五讲:高级考量与最佳实践

1. 异常规范与noexcept的再思考

  • 什么时候用 noexcept
    • 析构函数(必须是 noexcept,否则可能导致 std::terminate)。
    • 移动构造函数和移动赋值运算符(如果它们不抛异常,可以提供更好的性能保证,例如 std::vector 在扩容时会优先选择 noexcept 的移动操作)。
    • 不会失败的简单查询函数。
    • 底层、核心工具函数,如果它们失败表示程序逻辑严重错误,可以考虑 noexcept 并让其调用 std::terminate
  • 不要滥用 noexcept:如果一个函数可能抛出异常,却被标记为 noexcept,这会带来严重的运行时问题。

2. 设计模式的辅助

  • Memento模式(备忘录):可用于保存对象在操作前的完整状态,以便在失败时恢复。
  • Command模式:可以将一系列操作封装为命令对象,每个命令可以提供 execute()undo() 方法,从而构建复杂的事务日志。
  • Unit of Work模式:一个事务性操作集合,在事务结束时一次性提交所有变更。

3. 性能考量

实现强异常安全通常涉及复制数据或创建临时状态,这可能带来性能开销。

  • 复制成本:对于大型数据结构,深拷贝可能非常昂贵。需要权衡性能和异常安全性。
  • 日志/撤销栈的开销:记录每次变更的逆操作也会消耗内存和CPU。
  • 优化:在性能敏感的场景,可以考虑:
    • 使用写时复制(Copy-on-Write, COW)技术。
    • 对于不可变数据结构,每次修改都创建新版本,旧版本保持不变。
    • 在局部作用域内,如果可以确保操作不会抛出异常,可以避免额外的防御性拷贝。

4. 并发与异常安全

在多线程环境中,异常安全与并发控制(如互斥锁)的结合变得更加复杂。

  • RAII 锁std::lock_guard, std::unique_lock 是管理互斥锁的RAII方式,确保锁在异常发生时也能被正确释放。
  • 死锁风险:在事务中持有多把锁,如果回滚操作也需要锁,可能引入新的死锁风险。需要仔细设计锁的获取顺序和粒度。
  • 原子操作std::atomic 系列操作本身是无锁且异常安全的(它们不抛出异常)。但将多个原子操作组合成一个事务,仍需手动保证其原子性和异常安全。

5. 测试异常安全性

测试异常安全性比测试正常逻辑更具挑战性。

  • 故障注入:在代码中故意引入可能抛出异常的点(如模拟内存分配失败、文件I/O错误、网络中断),然后验证系统是否能正确回滚。
  • 单元测试:针对每个可能抛出异常的函数,编写测试用例,验证其在异常发生时是否满足对应的异常安全保证(基本、强、无抛出)。
  • Valgrind/ASan:使用内存错误检测工具检查资源泄露。

6. 避免在析构函数中抛出异常

这是C++中一个黄金法则。如果析构函数抛出异常,而此时正在进行栈展开(由于另一个异常),程序会立即调用 std::terminate。这会导致程序非预期终止,并且无法捕获或处理。析构函数应该总是 noexcept。如果析构函数中需要处理可能失败的操作,应该将这些操作移到另一个显式函数中(如 close()flush()),并在调用析构函数之前调用它。

7. 现代C++的错误处理:std::optionalstd::expected

C++17引入的 std::optional 和 C++23引入的 std::expected(或其Boost版本)提供了一种替代异常的错误处理方式。它们通过返回一个可能包含值或错误码/异常对象的类型,让调用者显式地检查操作结果。

  • std::optional<T>:表示一个值可能存在或不存在。适用于操作可能失败但没有具体错误信息的情况。
  • std::expected<T, E>:表示一个操作可能成功返回 T 类型的值,或者失败返回 E 类型的错误。这提供了一种更明确的错误处理机制,避免了异常的性能开销和栈展开的复杂性,尤其是在性能敏感或需要精确控制错误流的场景。

虽然 std::expected 可以减少异常的使用,但对于更复杂的“不成功则回滚”事务逻辑,异常仍然是处理不可预测的、无法局部处理的错误的强大工具。两者可以协同使用,各司其职。

结语

异常安全性,尤其是强异常安全性,是构建健壮、可靠C++系统的关键。它要求我们深入理解C++的异常机制、RAII原则,并运用Copy-and-Swap等设计模式。通过精心设计事务逻辑,我们能够确保在复杂操作中实现“不成功则回滚”的原子性,从而避免数据损坏和不一致状态。虽然实现强异常安全可能会引入额外的复杂性和性能开销,但在对数据一致性有严格要求的场景下,这是一项不可或缺的投资。

感谢各位的聆听。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注