Appearance
std::weak_ptr 详解
一、概念
weak_ptr 是观察者:指向由 shared_ptr 管理的对象,但不增加强引用计数(不参与所有权)。用于监测对象是否存在,需要真正使用时通过 lock() 提升为 shared_ptr。
cpp
std::shared_ptr<Foo> sp = std::make_shared<Foo>();
std::weak_ptr<Foo> wp = sp; // strong=1, weak=1
sp.reset(); // strong=0 → 对象被删;weak 计数随之减
if (auto s = wp.lock()) { // 提升成功(对象还活着)时才能安全使用
s->doSomething();
} else {
// 对象已被销毁
}二、底层原理
weak_ptr 内部也持有指向控制块的指针,但它增加的是弱引用计数(weak count),而非强引用计数。
- 强引用计数归零(use_count==0) → 删除对象本身。
- 但控制块不会立即释放:只有当强计数 + 弱计数都归零时,控制块才释放。
- 原因:
weak_ptr需要访问控制块里判断对象是否还活着、去lock;若对象删了就立即把控制块 free,weak_ptr 就成悬垂(不能 lock 也不能安全 expired)。
结论:对象的生命期由 strong 计数决定;控制块的释放由 (strong + weak) 计数决定。
三、核心方法
| 方法 | 说明 |
|---|---|
lock() | 若 use_count>0 返回 shared_ptr(提升),否则返回空 shared_ptr。线程安全,原子 |
expired() | 等价 use_count()==0(对象已销毁) |
use_count() | 被管理对象的强引用数 |
reset() | 放弃观测 |
owner_before | 用于做 map 的 key(提供严格弱序) |
四、高频问答
Q1. weak_ptr 有什么用?经典循环引用
cpp
struct A;
struct B;
struct A { std::shared_ptr<B> b; ~A(){ std::cout<<"~A\n";} };
struct B { std::shared_ptr<A> a; ~B(){ std::cout<<"~B\n";} };
int main() {
std::shared_ptr<A> pa = std::make_shared<A>();
std::shared_ptr<B> pb = std::make_shared<B>();
pa->b = pb; // A → B
pb->a = pa; // B → A(形成环)
// main 结束,pa/pb 各自离开但互相引用,计数永不为 0 → 泄漏 💥
}解决:把其中一方的 shared_ptr 改为 weak_ptr:
cpp
struct B { std::weak_ptr<A> a; }; // B 不拥有 A,只是观测当 pa 在 main 结束时,A 计数归零可析构;B 中 a 是 weak,不影响 A 析构,环被打破,B 正常析构。
Q2. 循环引用为什么一定会泄漏 / 在哪出问题?
环中每个对象都被环内另一个对象强引用兜住,即使外部所有权全部释放,内部引用计数仍 ≥1,析构函数永不被调用 → 内存(及析构副作用)永不释放。破解:让环中「不拥有」的一方用 weak_ptr 表达(谁是父/子所有权清晰)。
Q3. 怎么访问 weak_ptr 指向的对象?
必须 lock()(获得 shared_ptr 才能 ->)。weak_ptr 本身不支持 operator->/operator*,否则无法保证安全性。
Q4. 为什么 lock() 是线程安全的?
lock() 内部原子地判断 use_count:若 >0 则原子 +1 强计数并返回;若已为 0 返回空。这个「检查后提升」是原子的,避免「刚判断活着、下一刻被另一线程删掉再去加计数」的竞态。
Q5. weak_ptr 能直接比较 value 吗?
比较对象 (相等) 需转成 shared_ptr(若已过期无法比较值)。owner_before 用于在 std::set/std::map 中给 weak_ptr 排序(不需要对象存活,只比较所有者控制块地址)。
Q6. weak_ptr 的典型使用场景?
- 破除循环引用(最常见)。
- 缓存:缓存对象列表用 weak_ptr 监测,对象被外部删后自动失效(如资源缓存、观察者模式)。
- enable_shared_from_this 内部:继承该类时类内部保存一个
weak_ptr<T>(就是由创建它的 shared_ptr 初始化),成员调shared_from_this()时对该 weak_ptr 做lock()。
五、记忆口诀
- strong 计数为 0 → 对象销毁;
- strong + weak 都为 0 → 控制块销毁;
- 想看但不想拥有 →
weak_ptr+lock(); - 环上某向改用 weak_ptr → 打破循环。