好的,我们来解析这个条款的核心内容:
条款37的核心观点
该条款强调:当 std::thread 对象被销毁时,它必须处于不可结合的状态。否则,程序的行为是未定义的(通常会导致 std::terminate 被调用,程序终止)。
关键概念
- 默认构造后(未关联线程)。
- 被 join() 后(线程执行完毕,关联解除)。
- 被 detach() 后(关联被显式解除)。
- 被移动赋值后(原对象不再关联线程)。
- 被移动构造后(新对象获得关联,原对象不再关联)。
为何需要确保在所有路径上不可结合?
std::thread 的析构函数行为取决于其状态:
- 如果析构时处于不可结合状态:析构函数正常执行,无问题。
- 如果析构时处于可结合状态:析构函数会调用 std::terminate(),终止整个程序。
因此,我们必须确保在 std::thread 对象的生命周期结束前(即离开其作用域或被销毁前),通过 join() 或 detach() 将其变为不可结合状态。这包括所有可能的执行路径,例如:
- 正常执行路径。
- 函数提前返回(如 return, break, continue)。
- 抛出异常。
如何实现?
// 一个简单的RAII线程包装器示例
class ThreadRAII {
public:
enum class DtorAction { join, detach };
ThreadRAII(std::thread&& t, DtorAction a)
: action(a), t(std::move(t)) {}
~ThreadRAII() {
if (t.joinable()) { // 必须检查!
if (action == DtorAction::join) {
t.join();
} else {
t.detach();
}
}
}
std::thread& get() { return t; } // 提供访问原始线程的接口(谨慎使用)
private:
DtorAction action;
std::thread t;
};
// 使用示例
void someFunction() {
ThreadRAII tr(
std::thread([] { /* 线程任务 */ }),
ThreadRAII::DtorAction::join // 或 detach
);
// … 函数逻辑,可能抛出异常或提前返回
} // 无论函数如何退出,tr的析构函数都会确保线程变为不可结合
https://weibo.com/tv/show/1034:5275497211297795
https://weibo.com/tv/show/1034:5275497186394137
https://weibo.com/tv/show/1034:5275497173811211
https://weibo.com/tv/show/1034:5275497161228298
https://weibo.com/tv/show/1034:5275497136062479
https://weibo.com/tv/show/1034:5275497131606019
https://weibo.com/tv/show/1034:5275497102508043
https://weibo.com/tv/show/1034:5275497094119437
https://weibo.com/tv/show/1034:5275497052176402
https://weibo.com/tv/show/1034:5275497052176400
重要注意事项
- joinable() 检查:在调用 join() 或 detach() 前,必须检查线程是否处于可结合状态。对一个不可结合的线程调用这些函数是未定义行为。
- join vs detach:选择 join 还是 detach 取决于业务逻辑。join 等待线程结束,确保其任务完成;detach 让线程在后台独立运行,主线程不再等待和管理它。
- 移动语义:std::thread 是可移动但不可复制的。ThreadRAII 的构造函数使用了 std::move 来获取线程的所有权。
总结
Effective Modern C++ 条款37的核心是强制要求程序员管理好 std::thread 对象的生命周期,确保它们在销毁前变为不可结合状态,以避免程序意外终止。使用RAII技术是遵循这条建议、写出健壮多线程代码的最佳实践。



