必须用std::shared_ptr+显式拷贝传递,因其控制块引用计数线程安全,可防止use-after-free;但对象内部状态仍需手动同步,且每次传入新线程前必须拷贝而非传裸指针或引用。

为什么不能直接用裸指针在主从线程间传递对象
裸指针本身不带生命周期管理,一旦主线程释放了 new 出来的对象,而子线程还在通过该指针访问,就会触发未定义行为(use-after-free)。更隐蔽的问题是:即使对象没被释放,缺乏同步也会导致数据竞争——比如两个线程同时读写同一成员变量,结果不可预测。
常见错误现象包括:
- 程序随机崩溃(SIGSEGV 或 SIGBUS)
- 对象字段值“跳变”或为零(未完成构造就被读取)
-
std::shared_ptr析构时抛出std::bad_weak_ptr(多线程中weak_ptr::lock()与析构竞态)
必须用 std::shared_ptr + 显式拷贝传递
std::shared_ptr 是线程安全的控制块引用计数(C++11 起标准保证),但其指向的对象本身仍需手动同步。关键点在于:每次传给新线程前,必须显式拷贝 shared_ptr,而不是传递原始指针或引用。
使用场景示例:
- 主线程创建对象并启动子线程处理
- 子线程需长期持有该对象(如异步回调上下文)
- 多个子线程共享同一配置对象
实操建议:
- 主线程中用
auto obj = std::make_shared<myclass>(...)</myclass>创建 - 启动线程时,把
obj拷贝进 lambda 捕获列表:std::thread{[obj]{ /<em> 使用 obj->method() </em>/ }} - 不要捕获
&obj或obj.get(),否则子线程可能访问已销毁的控制块或对象
对象内部状态需要额外同步
std::shared_ptr 只保住了对象内存不被提前释放,但多个线程对对象内字段的并发读写仍需保护。不能依赖 shared_ptr 的线程安全性来替代数据同步。
参数差异提醒:
-
std::shared_ptr的引用计数增减是原子的,但operator->()返回的原始指针不提供任何同步 - 若对象方法只读且无副作用(如
get_name()),可不加锁;但只要涉及修改(如set_value(int))或读-改-写(如increment_counter()),就必须加锁
性能影响注意:
- 频繁拷贝
shared_ptr开销极小(仅原子整数操作),远小于锁竞争 - 但若子线程只需短暂访问,且主线程能确保对象生命周期覆盖整个子线程运行期,也可考虑用
std::atomic<myclass></myclass>+ 手动生命周期管理(风险高,不推荐新手)
避免 std::weak_ptr 在子线程里“裸用”
weak_ptr::lock() 返回 shared_ptr 是线程安全的,但返回空指针后继续访问会导致崩溃。很多开发者误以为“先 lock 再判断”就万无一失,却忽略了 lock 成功后对象仍可能被其他线程在下一毫秒释放——除非你在 lock 后立刻完成全部操作。
容易踩的坑:
- 在子线程中反复调用
weak_ptr::lock()并缓存结果(缓存的是过期的shared_ptr) - 把
weak_ptr直接传给子线程,却不确保主线程不会提前重置它 - 忘记检查
lock()返回值是否为空,直接解引用
正确做法是:子线程中每次需要访问时都调用 lock(),检查非空后再用,且单次调用内完成所有操作(不要跨多次 lock 保留中间状态)。
复杂点往往藏在生命周期边界上:主线程何时释放 shared_ptr、子线程是否可能比主线程活得更久、对象析构函数里是否还存在线程未退出——这些地方稍有疏忽,调试起来就很难复现。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











