引言
上一篇我们学习了 shared_ptr------通过引用计数实现共享所有权。但引用计数有一个致命缺陷:循环引用。
两个对象互相持有对方的 shared_ptr,引用计数永远无法归零------即使你已经无法访问它们,它们仍然互相"拽着"对方,谁也释放不了。这就是内存泄漏。
weak_ptr 就是为解决这个问题而生的。它指向 shared_ptr 管理的对象,但不增加引用计数 。当最后一个 shared_ptr 销毁时,对象照常释放,weak_ptr 会自动变为空。

第一部分:循环引用的灾难
一、一个内存泄漏的实例
cpp
#include <memory>
#include <iostream>
using namespace std;
class B; // 前向声明
class A {
public:
shared_ptr<B> ptrB;
~A() { cout << "A 析构" << endl; }
};
class B {
public:
shared_ptr<A> ptrA; // ← 如果是 shared_ptr,循环引用!
~B() { cout << "B 析构" << endl; }
};
int main() {
auto a = make_shared<A>();
auto b = make_shared<B>();
a->ptrB = b; // a 持有 b
b->ptrA = a; // b 持有 a → 循环!
// a 和 b 离开作用域
// 但它们的引用计数各为 1(对方持有的)
// → 永远不会析构 → 内存泄漏!
}
运行结果 :什么也不输出。A 和 B 的析构函数永远不会被调用。
二、循环引用的形成过程

第二部分:weak_ptr 的原理
一、weak_ptr 是什么
weak_ptr 是一种不控制对象生命周期 的智能指针。它指向 shared_ptr 管理的对象,但:
-
不增加引用计数
-
不影响对象的释放时机
-
当对象被释放后,
weak_ptr自动变为空(不会悬空)

二、基本操作
cpp
auto sp = make_shared<int>(42);
weak_ptr<int> wp = sp; // 从 shared_ptr 创建
// 检查对象是否还存在
if (!wp.expired()) {
cout << "对象还活着" << endl;
}
// 获取 shared_ptr(锁定)
if (auto locked = wp.lock()) {
// locked 是一个 shared_ptr,引用计数 +1
cout << *locked << endl;
} // locked 离开作用域,引用计数 -1
sp.reset(); // 释放对象
if (wp.expired()) {
cout << "对象已被释放" << endl;
}
// 对象已释放,lock() 返回空 shared_ptr
auto locked = wp.lock();
if (!locked) {
cout << "lock() 返回空" << endl;
}
| 操作 | 含义 |
|---|---|
wp.expired() |
检查对象是否已被释放 |
wp.lock() |
返回一个 shared_ptr(如果对象还活着) |
wp.use_count() |
返回 shared_ptr 的引用计数 |
wp.reset() |
清空 weak_ptr |
三、为什么 lock() 是原子操作
cpp
// ❌ 不是线程安全的
if (!wp.expired()) { // 步骤1:检查
auto sp = wp.lock(); // 步骤2:锁定
// 步骤1 和 步骤2 之间,对象可能被另一个线程释放!
}
// ✅ lock() 是原子操作
if (auto sp = wp.lock()) { // 一步完成
// 安全使用 sp
}
第三部分:解决循环引用
cpp
class B;
class A {
public:
shared_ptr<B> ptrB; // A 持有 B(强引用)
~A() { cout << "A 析构" << endl; }
};
class B {
public:
weak_ptr<A> ptrA; // B 持有 A(弱引用!不增加计数)
~B() { cout << "B 析构" << endl; }
};
int main() {
auto a = make_shared<A>();
auto b = make_shared<B>();
a->ptrB = b; // a 持有 b → B 引用计数 = 2
b->ptrA = a; // b 持有 a → A 引用计数 = 1(weak_ptr 不增加!)
// main 结束,a 和 b 销毁:
// a 销毁 → A 引用计数 = 0 → A 析构
// → A 析构后,A::ptrB 销毁 → B 引用计数 -1
// b 销毁 → B 引用计数 = 0 → B 析构 ✅
}
运行结果:
A 析构
B 析构
为什么能解决?

第四部分:weak_ptr 的使用场景
一、观察者模式
cpp
class Subject {
private:
vector<weak_ptr<Observer>> observers; // 弱引用观察者
public:
void addObserver(shared_ptr<Observer> obs) {
observers.push_back(obs);
}
void notify() {
for (auto& weak : observers) {
if (auto obs = weak.lock()) { // 观察者还活着
obs->update();
} else {
// 观察者已销毁,可以清理 weak_ptr
}
}
}
};
// 观察者释放后,Subject 不会阻止其销毁
二、缓存系统
cpp
class Cache {
private:
map<int, weak_ptr<Data>> cache;
public:
shared_ptr<Data> get(int key) {
auto it = cache.find(key);
if (it != cache.end()) {
if (auto data = it->second.lock()) {
return data; // 缓存命中
}
// 数据已被外部释放,清理缓存
cache.erase(it);
}
auto data = make_shared<Data>(key);
cache[key] = data;
return data;
}
};
// 缓存持有 weak_ptr,外部用完了数据自然释放
三、打破 shared_ptr 循环引用

第五部分:三种智能指针总结
| 对比 | unique_ptr | shared_ptr | weak_ptr |
|---|---|---|---|
| 所有权 | 独占 | 共享 | 不拥有(弱引用) |
| 拷贝 | ❌ | ✅(计数+1) | ✅(不增加计数) |
| 移动 | ✅ | ✅ | ✅ |
| 大小 | 8 字节 | 16 字节 | 16 字节 |
| 释放 | 自动 | 计数归零自动 | 不影响释放 |
| 循环引用 | 不存在此问题 | 会内存泄漏 | 解决循环引用 |
| 创建 | make_unique |
make_shared |
从 shared_ptr 创建 |
使用原则

总结
一、核心要点
| 要点 | 内容 |
|---|---|
| 本质 | 弱引用,不增加引用计数,不控制对象生命周期 |
| 核心操作 | lock() 获取 shared_ptr(原子操作)、expired() 检查对象是否存活 |
| 主要用途 | 打破 shared_ptr 循环引用、实现观察者模式、缓存系统 |
| 创建 | 从 shared_ptr 创建:weak_ptr<T> wp = sp; |
二、一句话记忆
weak_ptr 是 shared_ptr 的弱引用搭档,指向对象但不增加引用计数。用 lock() 原子地获取一个 shared_ptr 来安全访问对象。主要用来打破循环引用------父子关系中父用 shared_ptr、子用 weak_ptr。