回滚依赖深拷贝而非指针引用;状态备份安全取决于存储的是值还是地址,需确保数据成员支持廉价拷贝或显式深拷贝,避免裸指针、全局变量及伪拷贝,rollback/commit 应 noexcept 且考虑异常安全与线程隔离。

回滚依赖深拷贝还是指针引用?
状态备份是否安全,取决于你存的是值还是地址。如果只保存 std::shared_ptr 或裸指针,回滚时还原的只是旧指针,实际对象可能已被修改或析构;若用 std::unique_ptr 且未定义拷贝语义,直接复制会编译失败。
正确做法是:对需回滚的数据成员,确保其类型支持廉价拷贝(如 int、std::string、std::vector),或显式实现深拷贝逻辑。例如:
struct Account {
int balance;
std::string owner;
// 支持拷贝构造 —— 回滚时靠它重建旧状态
Account(const Account& other) : balance(other.balance), owner(other.owner) {}
};
- 避免在事务中修改全局单例或静态变量,它们无法被自动备份
- 若必须管理资源(如文件句柄、socket),应在备份前封装为 RAII 对象,并在回滚时触发其析构
- 不建议用
memcpy或std::bit_cast做“伪拷贝”,对含虚函数、引用、指针的类极易 UB
如何在 commit/rollback 时避免二次异常?
std::uncaught_exceptions() 是关键信号。C++17 起可用它判断当前是否已处于栈展开中——若在析构函数里调 rollback(),而 rollback 又抛异常,程序直接 terminate。
实操上,事务类的析构应无异常:
~Transaction() noexcept {
if (active_ && std::uncaught_exceptions() == 0) {
commit();
} else if (active_) {
rollback(); // 此处 rollback 内部必须 noexcept
}
}
- 所有
rollback()操作应标记为noexcept,内部禁止 throw;可改用错误码或日志记录失败 - commit 阶段若需持久化(如写磁盘),应提前检查权限/空间,而非等到析构时才发现失败
- 避免在
rollback()中调用可能抛异常的 STL 算法(如带自定义比较器的std::sort)
备份时机放在 begin() 还是第一次写操作?
延迟备份(lazy snapshot)更高效,但必须精确拦截所有可变操作。常见误判是:只重载 operator=,却忘了 push_back、insert、clear 等成员函数。
推荐组合策略:
- 在
begin()仅初始化事务上下文(如事务 ID、时间戳),不拍快照 - 首次对受管对象调用非常量成员函数时,触发该对象的
backup_if_needed() - 使用代理类(如
TransactionalVector)封装容器,把备份逻辑内聚到数据结构内部 - 若用宏或模板注入(如 CRTP),注意 SFINAE 失败会导致备份静默失效,务必加
static_assert校验
多线程下备份与还原为何出现数据竞争?
一个线程在 rollback() 中正用旧值覆盖成员,另一个线程同时调 get_balance() 读取——这不是原子操作,结果不可预测。
根本解法不是加锁整个事务,而是分层隔离:
- 每个线程拥有独立事务实例,备份数据存在 TLS(
thread_local)或栈上 - 共享状态(如数据库连接池)由外部同步控制,事务类只负责本线程内状态一致性
- 若需跨线程回滚(如分布式事务),备份本身无意义,应转用补偿事务(Saga)模式
最容易被忽略的一点:即使用了 std::mutex,也要确认 lock/unlock 严格配对——在异常路径中漏 unlock 会导致后续所有事务卡死。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











