循环引用发生在两个对象互相持有对方的shared_ptr时,导致引用计数无法归零;将其中一个改为weak_ptr可打破循环,因其不增加引用计数,仅观察对象是否存在,从而避免内存泄漏。

在C++中,weak_ptr 主要用来解决 shared_ptr 可能引发的循环引用问题。当两个或多个对象通过 shared_ptr 相互持有对方时,引用计数永远无法归零,导致内存泄漏。weak_ptr 提供了一种“观察”资源的方式,而不增加引用计数,从而打破这种循环。
循环引用是如何发生的?
考虑两个类 A 和 B,它们各自持有一个指向对方的 shared_ptr:
class B; // 前向声明class A {
public:
std::shared_ptr<B> ptr;
~A() { std::cout << "A destroyed\n"; }
};
class B {
public:
std::shared_ptr<A> ptr;
~B() { std::cout << "B destroyed\n"; }
};
如果这样使用:
auto a = std::make_shared<A>();auto b = std::make_shared<B>();
a->ptr = b;
b->ptr = a;
此时,a 和 b 的引用计数都是2。当作用域结束时,a 和 b 的局部引用被释放,引用计数减为1,但由于彼此仍互相引用,析构函数不会被调用,造成内存泄漏。
立即学习“C++免费学习笔记(深入)”;
weak_ptr 如何打破循环?
将其中一个 shared_ptr 改为 weak_ptr,即可打破循环引用。weak_ptr 不增加引用计数,只是“弱引用”或“观察”目标对象是否存在。
修改上面的例子:
class B;class A {
public:
std::shared_ptr<B> ptr;
~A() { std::cout << "A destroyed\n"; }
};
class B {
public:
std::weak_ptr<A> ptr; // 改为 weak_ptr
~B() { std::cout << "B destroyed\n"; }
};
现在,只有 A 持有 B 的强引用,而 B 持有 A 的弱引用。当外部引用 a 和 b 离开作用域时:
- a 的引用计数从1降到0,A 被销毁
- A 销毁后,其持有的 b 引用减少,B 的引用计数也归零,B 被销毁
- 整个资源被正确释放
weak_ptr 的使用方式
由于 weak_ptr 不保证所指对象仍然存在,访问前必须先检查:
std::shared_ptr<A> lock_ptr = b.ptr.lock();if (lock_ptr) {
// 对象还活着,可以安全使用
std::cout << "A is still alive\n";
} else {
// 对象已被销毁
std::cout << "A has been destroyed\n";
}
lock() 方法尝试获取一个 shared_ptr,如果原对象已释放,返回空 shared_ptr。
典型应用场景
- 父-子结构:父节点用 shared_ptr 持有子节点,子节点用 weak_ptr 指向父节点,避免循环
- 缓存系统:缓存表用 weak_ptr 观察对象,不阻止其销毁
- 观察者模式:观察者用 weak_ptr 弱引用目标,防止因循环引用导致内存泄漏
基本上就这些。weak_ptr 不复杂但容易忽略,合理使用能有效避免 shared_ptr 的循环引用陷阱。关键是理解它“只观察、不拥有”的特性。











