循环引用指两个对象的shared_ptr相互持有,导致引用计数无法归零而内存泄漏;weak_ptr不增加引用计数,可打破循环。

在c++中,std::shared_ptr通过引用计数管理对象生命周期,但当两个或多个对象相互持有对方的shared_ptr时,就会出现循环引用问题。这会导致对象无法被正确释放,从而引发内存泄漏。std::weak_ptr正是为了解决这个问题而设计的。
什么是循环引用?
考虑两个类A和B,它们彼此持有对方的shared_ptr:
class B; // 前向声明
class A {
public:
std::shared_ptr<B> ptr;
~A() { std::cout << “A destroyedn”; }
};
class B {
public:
std::shared_ptr<A> ptr;
~B() { std::cout << “B destroyedn”; }
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->ptr = b;
b->ptr = a; // 循环形成
}
此时,a和b的引用计数都为2。当main函数结束时,a和b离开作用域,各自引用计数减1,变为1,但由于仍存在指向它们的shared_ptr(在对方内部),析构函数不会被调用,造成内存泄漏。
weak_ptr如何打破循环
std::weak_ptr不增加引用计数,它只是“观察”一个由shared_ptr管理的对象。你可以通过lock()方法临时获取一个shared_ptr来安全访问对象,如果对象已被释放,则返回空的shared_ptr。
立即学习“C++免费学习笔记(深入)”;
修改上面的例子,让其中一个类使用weak_ptr:
class B;
class A {
public:
std::shared_ptr<B> ptr;
~A() { std::cout << “A destroyedn”; }
};
class B {
public:
std::weak_ptr<A> ptr; // 改成 weak_ptr
~B() { std::cout << “B destroyedn”; }
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->ptr = b;
b->ptr = a; // 不增加引用计数
}
现在,a的引用计数为2(外部a + b.ptr.lock()),但b的引用计数只有1(外部b)。当main结束,b先被释放,然后a的引用计数变为1再变为0,最终a也被释放。析构顺序正常执行,内存得以回收。
何时使用weak_ptr
在以下场景中推荐使用weak_ptr:
- 父子关系中,子对象持有父对象的弱引用
- 缓存或监听器机制中,避免因强引用导致对象无法释放
- 回调函数中传递对象引用,防止生命周期被意外延长
- 任何可能出现双向引用的结构中,选择一方使用weak_ptr
访问weak_ptr时需注意:必须通过lock()获取shared_ptr,不能直接解引用。例如:
if (auto locked = b->ptr.lock()) {
// 安全使用 locked
std::cout << “A is still aliven”;
} else {
std::cout << “A has been destroyedn”;
}
基本上就这些。只要在可能形成闭环的地方用weak_ptr替代其中一个shared_ptr,就能有效避免循环引用带来的内存泄漏问题。关键是要理清对象间的所有权关系——拥有权用shared_ptr,观察用weak_ptr。