C++ RAII 线程生命周期:让退出路径也遵守所有权

背景 构造线程之后抛出异常,如果对象仍持有 joinable 的 std::thread,退出路径可能直接终止程序。线程拥有关系需要覆盖异常。 核心判断 C++20 可用 jthread 表达请求停止与等待的拥有关系。 工作循环检查 stop_token,阻塞操作也要有可唤醒路径。 析构顺序保证线程先退出,再销毁它引用的资源。 最小示例 下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。 std::jthread worker([](std::stop_token stop) { while (!stop.stop_requested()) do_one_iteration(); }); 边界与误区 jthread 的析构会请求停止并等待,但不会神奇打断任意阻塞 I/O。持锁等待线程结束也可能形成死锁。 验证方式 测试异常退出、空闲阻塞和执行中停止,记录析构等待时间;故意调整成员析构顺序,确认被访问资源不会先于线程销毁。 小结 线程拥有者必须负责 join 或请求停止,异常路径不能留下 joinable 线程。

2026年9月23日 · 1 分钟 · BvBeJ

C++异常安全保证:从RAII到noexcept策略

背景与问题界定 在金融交易系统的风控引擎中,一个诡异的bug引发了整个交易链路的异常终止:某个字段的std::vector在push_back时因内存不足抛出std::bad_alloc,异常沿调用栈向上传播,导致一个持有互斥锁的作用域被stack unwinding跳过——锁没有释放。这是一个经典的"异常安全基本保证违反"案例。更广泛的问题在于,整个代码库的大部分函数没有明确声明其异常安全级别(Nothrow / Strong / Basic / No guarantee),部分函数混用了throw和返回错误码两种模式,导致调用方无法确定何时应该catch、何时应该检查返回值。C++11/17/20提供了更多的工具(noexcept、move semantics、smart pointers)来实现异常安全,但正确的使用需要贯穿整个架构层面的设计纪律。 目标拆解与工程约束 异常安全级别标注:每个函数需要明确其异常安全保证。C++17引入了noexcept作为类型系统的一部分,noexcept函数可以提供"nothrow"保证。但对于提供Strong或Basic保证的函数,语言层面没有直接标注机制,需要通过文档或convention维持纪律。 Move构造与异常的冲突:std::vector在realloc时,C++11之前的copy+delete操作是异常安全的(如果copy抛出,旧元素还在)。Move构造如果在realloc过程中抛出(通常因为move不是noexcept),vector无法回滚到原始状态。因此,所有用于容器的类型必须提供noexcept的move constructor和move assignment。 异常与线程交互:一个线程中抛出的异常不能被另一个线程catch。C++11的std::exception_ptr可以将异常跨线程传递,但生命周期管理容易出错。交易系统中的异步操作(通过线程池提交的任务)需要统一捕获异常并通过std::future或回调方式传播。 继承与虚函数的异常规范:基类虚函数声明非noexcept,派生类的override也不能是noexcept(反之可以)。如果基类虚函数未声明异常规范,派生类override必须假设它可能会抛出任何异常,这导致类型系统的异常信息完全丢失。 方案设计 我们建立了三级异常安全体制: 第一级:基础设施层的强保证(Strong Guarantee)。对涉及外部系统状态变更的操作(数据库写入、消息发送、文件替换),采用"Commit-or-Rollback"模式。用RAII guard在成功写入后方可"提交",任何异常抛出都让guard的析构函数完成回滚。 class DbWriteGuard { std::function<void()> rollback_; bool committed_ = false; public: DbWriteGuard(std::function<void()> rollback) : rollback_(std::move(rollback)) {} ~DbWriteGuard() { if (!committed_) rollback_(); } void commit() noexcept { committed_ = true; } // 标记成功 }; void transfer_funds(Account& from, Account& to, int64_t amount) { auto guard = DbWriteGuard([&] { undo_transfer(from, to, amount); }); from.balance -= amount; to.balance += amount; guard.commit(); // 如果上面任何一行抛出,undo_transfer自动执行 } 第二级:核心逻辑层的Nothrow保证(Noexcept Guarantee)。在性能关键路径上的函数全部标记noexcept,通过前置条件检查确保不会产生异常。这包括所有move构造函数、swap函数、以及热路径上的访问器。 ...

2026年7月29日 · 1 分钟 · BvBeJ