直接用 std::shared_ptr 在异步回调中会崩溃,因对象可能已销毁但 shared_ptr 仍非空;应改用 weak_ptr::lock() 安全获取 shared_ptr,仅当对象存活时才执行后续操作。

为什么直接用 std::shared_ptr 在异步回调里会崩溃
异步回调(比如线程池任务、定时器触发、网络响应)常在对象已销毁后才执行,此时若回调里还持有 std::shared_ptr<t></t> 并调用成员函数,可能访问已释放内存 —— 尤其当 T 的析构函数已运行、但 shared_ptr 的引用计数还没归零(例如还有其他 owner)时,shared_ptr 仍非空,但对象状态不可用。
根本问题不是“指针空不空”,而是“对象是否还活着且可安全使用”。weak_ptr 的核心价值就在这里:它不延长对象生命周期,且能明确告诉你“此刻对象是否还在”。
lock() 是唯一安全的访问入口,别跳过它
std::weak_ptr::lock() 返回一个 std::shared_ptr,仅当所指向对象仍存活时才非空;否则返回空 shared_ptr。这是原子性检查 + 提升的组合操作,不可拆开。
- ❌ 错误写法:
if (wp.expired()) return;然后直接用另一个shared_ptr(可能已失效) - ✅ 正确写法:始终用
auto sp = wp.lock(); if (!sp) return;,后续只基于sp操作 - ⚠️ 注意:
lock()成功后,sp保证对象至少在当前作用域内存活,但不能保证跨函数调用期间仍有效(除非你持续持有sp)
典型异步场景下的模式:捕获 weak_ptr,回调里先 lock()
以 lambda 捕获为例,常见于 std::async、std::thread 或第三方异步库(如 libuv、boost.asio):
class Handler {
public:
void start_async() {
auto weak_this = std::weak_ptr<handler>(shared_from_this());
std::async(std::launch::async, [weak_this]() {
auto self = weak_this.lock();
if (!self) return; // 对象已销毁,直接退出
self->on_data_ready(); // 安全调用
});
}
private:
void on_data_ready() { /* ... */ }
};
</handler>
关键点:
- 必须从
shared_from_this()构造weak_ptr,不能从裸指针或局部shared_ptr构造(否则lock()永远失败) - lambda 按值捕获
weak_ptr,避免生命周期依赖外部变量 - 不要在
lock()前做任何可能阻塞或耗时的操作(比如 I/O、锁等待),否则增加对象被销毁的窗口期
容易被忽略的陷阱:多线程下 weak_ptr 本身不是线程安全的?
std::weak_ptr 的拷贝、赋值、lock() 都是线程安全的 —— 标准明确要求。但注意:
- ⚠️ 同一个
weak_ptr实例被多个线程同时调用lock()没问题 - ⚠️ 但若一个线程正在析构对应对象(即最后一个
shared_ptr被销毁),另一线程调用lock()可能返回空,这是预期行为,不是 bug - ⚠️ 真正危险的是:把
weak_ptr存在全局容器里,又没加锁控制其生命周期管理(比如未同步地 erase + lock)—— 这属于设计问题,不是weak_ptr的缺陷
复杂点往往不在 weak_ptr 本身,而在于你如何组织对象生命周期和异步任务的取消逻辑。比如:任务启动后对象立即销毁,但异步任务无法被主动 cancel,只能靠 lock() 被动防御 —— 这种场景下,weak_ptr 是底线,不是万能解药。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











