weak_ptr::lock()是安全访问shared_ptr管理对象的唯一原子操作,必须检查返回shared_ptr是否非空;禁用expired(),避免因竞态导致悬空指针;lock成功后若需多次访问,应重新lock或拷贝数据。

std::shared_ptr + weak_ptr 配合 lock() 解决共享对象生命周期不确定性
高并发下最常踩的坑,是多个线程同时访问一个被 shared_ptr 管理的对象,但该对象可能在任意时刻被其他线程析构——这时若直接解引用,就是悬空指针。根本原因不是锁没加,而是“对象是否还活着”这个状态本身不具备原子性。
正确做法永远是:先用 weak_ptr::lock() 尝试获取临时 shared_ptr,再检查是否非空。这个操作是原子的,能安全应对对象正在销毁的竞态窗口。
-
weak_ptr::expired()不要用——它只是读引用计数,和后续lock()之间存在竞争,不安全 - 不要把
lock()结果存为全局或长期持有的shared_ptr,否则会意外延长对象生命周期,干扰资源释放节奏 - 如果业务逻辑耗时较长,且允许对象在中间被销毁,应在关键步骤前再次调用
lock()重新确认
std::unique_ptr 移动语义避免跨线程传递裸指针
想把资源从一个线程交给另一个线程?别传 shared_ptr,也别传裸指针。用 std::unique_ptr 的移动语义才是零开销、无竞争的方案。
移动操作本身不涉及引用计数变更,也不需要锁保护;只要确保源端不再访问、目标端只由单一线程持有,就天然线程安全。
立即学习“C++免费学习笔记(深入)”;
- 函数返回
std::unique_ptr是安全的,编译器会自动移动(C++11 起) - 用
std::move()显式转移所有权后,原变量变为nullptr,解引用会触发未定义行为,建议立刻置空或用assert(ptr)防御 - 禁止在构造函数里把
unique_ptr成员“转发”给其他线程——此时对象尚未完全构造,可能引发部分初始化访问
RAII 锁对象必须严格限定作用域,避免析构阻塞
用 std::lock_guard 或 std::unique_lock 封装互斥量没问题,但一旦析构函数里做了耗时操作(比如发网络请求、刷磁盘),就会拖慢整个线程调度——尤其当多个线程同时释放同一个 shared_ptr,而其析构函数又持锁时,极易死锁。
这是 RAII 在高并发中最隐蔽的陷阱:语法上很干净,语义上却埋了雷。
- 析构函数里只做轻量清理:关文件描述符、释放内存、解锁互斥量
- 把日志、序列化、RPC 等耗时操作移出析构,改用异步队列或延迟回调
- 对高频临界区,优先考虑
std::shared_mutex(读多写少)或无锁结构(如std::atomic+ CAS 循环),而不是靠锁粒度细化硬扛
std::weak_ptr::lock() 返回空指针时该怎么处理
返回 nullptr 不代表错误,而是明确告诉你:“这个对象已经不在了”。强行重试或忽略,都会让逻辑偏离预期。
典型处理方式取决于场景:
- 观察型任务(如监控、日志):直接跳过,继续下一轮循环
- 命令型任务(如修改某节点状态):返回失败码,由上层决定重试或降级
- 资源依赖型(如渲染帧需某个纹理):提前预加载并用
shared_ptr持有关键资源,避免运行时失效
最容易被忽略的一点:lock() 成功拿到 shared_ptr 后,仍要假设对象可能被其他线程在任意时刻销毁——尤其是当你要多次访问其成员时,每次访问前都应重新校验,或把所需数据一次性拷贝出来再处理。


















