构造函数中直接用std::thread{this}会崩溃,因对象未构造完成,成员变量可能未初始化,且线程生命周期可能超越对象;应改用工厂函数+std::shared_ptr延迟启动,并在析构前join()或使用C++20的std::jthread配合stop_token。

构造函数里直接 std::thread{this} 会崩溃
绝大多数人第一次尝试在构造函数里开线程,写的都是类似 std::thread{&MyClass::worker, this} 这种代码。编译能过,但运行时大概率崩溃或读到垃圾值。根本原因是:对象的构造尚未完成,this 指针虽已存在,但成员变量可能还没初始化完毕;而新线程一旦抢先进入 worker,就可能访问未定义状态的成员。
更隐蔽的问题是生命周期错配:如果线程被 detach(),它可能在对象析构后继续运行,访问已销毁的内存——这种 bug 往往只在压力测试或特定调度下才暴露。
- 绝对不要在构造函数体内调用
std::thread并传this - 禁止在构造函数中注册回调、绑定成员函数、或把
this交给任何异步上下文 - 即使加了
std::mutex也救不了——锁解决不了“对象还没建好”的问题
用工厂函数 + std::shared_ptr 延迟启动线程
安全的做法是把线程启动推迟到对象完全构造完成之后。最常用且 RAII 友好的方案是工厂函数配合 std::shared_ptr:
class Worker {
public:
static std::shared_ptr<worker> create() {
auto ptr = std::make_shared<worker>();
ptr->start_thread(); // 此时构造已完成
return ptr;
}
<p>private:
Worker() = default; // 私有构造,防止栈上创建
void start<em>thread() {
thread</em> = std::thread{&Worker::run, this};
}
void run() { /<em> 实际工作 </em>/ }</p>
<pre class="brush:php;toolbar:false;">std::thread thread_;
};
这样做的关键点:
-
std::shared_ptr确保对象生命周期由引用计数管理,只要线程还活着,对象就不会被析构 - 线程内部必须用
std::weak_ptr或显式检查对象是否仍有效(尤其在循环任务中) - 析构函数里要确保线程已
join(),否则std::thread析构会触发std::terminate
析构时必须 join,不能 detach
协作式线程意味着它需要和对象共存亡。一旦对象析构,线程若还在跑,就是悬垂指针。所以 detach() 在这类场景中是反模式。
正确做法是在析构函数中主动 join():
~Worker() {
if (thread_.joinable()) {
thread_.join(); // 阻塞等待线程结束
}
}
但要注意:如果 run() 是一个长循环且没有退出机制,join() 就会永远卡住。因此必须配套设计退出信号:
- 用
std::atomic作为线程退出标志stop_requested_ - 线程内定期检查该标志,收到信号后清理并返回
- 析构前先置
stop_requested_ = true,再join()
std::jthread 是 C++20 的更优解
C++20 引入 std::jthread,它在构造时自动管理 join,析构时自动调用 join()(除非已手动 detach()),同时内置了停止令牌(std::stop_token)支持协作式中断。
改写上面的类只需几处变化:
class Worker {
public:
static std::shared_ptr<worker> create() {
auto ptr = std::make_shared<worker>();
ptr->start_thread();
return ptr;
}
<p>private:
void start<em>thread() {
thread</em> = std::jthread{&Worker::run, this};
}
void run(std::stop_token stoken) {
while (!stoken.stop_requested()) {
// 工作逻辑
}
}</p>
<pre class="brush:php;toolbar:false;">std::jthread thread_;
};
相比 std::thread,std::jthread 消除了析构时忘记 join() 的风险,且原生支持停止语义。但它要求编译器支持 C++20,且不能跨平台用于老系统。
真正容易被忽略的是:即使用了 std::jthread,你依然得确保线程函数能响应 stop_token —— 它不会强制终止线程,只是提供通知机制。阻塞在 std::condition_variable::wait 或 sleep_for 中的线程,必须用带 stop_token 的重载版本才能及时退出。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











