在C++的智能指针体系中,shared_ptr通过引用计数管理对象生命周期,当两个或多个shared_ptr互相持有对方的引用时,就会产生循环引用,导致引用计数永远无法降为0,对象无法释放,最终造成内存泄漏。weak_ptr作为辅助型智能指针,刚好可以解决这一问题。

shared_ptr循环引用的产生原因
shared_ptr的核心是引用计数,当一个shared_ptr指向某个对象时,该对象的引用计数加1;当shared_ptr析构时,引用计数减1。当引用计数降为0时,对象才会被释放。
循环引用通常发生在两个互相引用的对象场景中,比如有两个类A和B,A的对象持有指向B的shared_ptr,B的对象持有指向A的shared_ptr,当这两个对象互相引用后,各自的引用计数都会至少为2,即使外部不再持有这两个对象的指针,引用计数也不会降为0,对象无法释放。
循环引用示例代码
下面的代码演示了shared_ptr循环引用的问题:
#include <iostream>
#include <memory>
class B; // 前向声明
class A {
public:
std::shared_ptr<B> b_ptr;
~A() {
std::cout << "A对象被销毁" << std::endl;
}
};
class B {
public:
std::shared_ptr<A> a_ptr;
~B() {
std::cout << "B对象被销毁" << std::endl;
}
};
int main() {
// 创建A和B的智能指针
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
// 互相引用,形成循环
a->b_ptr = b;
b->a_ptr = a;
// 此时a和b的引用计数都是2
std::cout << "A引用计数: " << a.use_count() << std::endl;
std::cout << "B引用计数: " << b.use_count() << std::endl;
// main函数结束时,a和b析构,引用计数各减1,但都还是1,不会触发对象销毁
return 0;
}
运行上述代码会发现,程序结束时没有输出A和B的析构信息,说明两个对象都没有被释放,这就是循环引用导致的内存泄漏。
weak_ptr的工作原理
weak_ptr是一种不控制对象生命周期的智能指针,它指向一个由shared_ptr管理的对象,但是不会增加该对象的引用计数。weak_ptr的构造和析构不会影响引用计数,它只是作为一个观察者存在。
weak_ptr不能直接访问对象,需要通过调用lock()方法尝试获取对应的shared_ptr,如果对象还存在,lock()会返回一个有效的shared_ptr,否则返回空的shared_ptr。这个特性让weak_ptr可以打破循环引用:当两个对象中的一个使用weak_ptr持有另一个对象的引用时,不会增加对方的引用计数,循环引用就会被打破。
用weak_ptr解决循环引用的方案
解决循环引用的核心思路是:将循环引用中的其中一方的shared_ptr换成weak_ptr。通常我们会把子对象的shared_ptr持有换成weak_ptr持有,因为子对象的生命周期往往不应该由父对象控制,或者反过来根据实际的业务关系调整。
修改后的代码示例
我们将上面示例中的B类持有的A的shared_ptr改为weak_ptr,就可以解决循环引用问题:
#include <iostream>
#include <memory>
class B; // 前向声明
class A {
public:
std::shared_ptr<B> b_ptr;
~A() {
std::cout << "A对象被销毁" << std::endl;
}
};
class B {
public:
// 将shared_ptr改为weak_ptr,不增加A的引用计数
std::weak_ptr<A> a_ptr;
~B() {
std::cout << "B对象被销毁" << std::endl;
}
};
int main() {
// 创建A和B的智能指针
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
// 互相引用,此时B持有的是weak_ptr,不会增加a的引用计数
a->b_ptr = b;
b->a_ptr = a;
// 此时a的引用计数是1,b的引用计数是2
std::cout << "A引用计数: " << a.use_count() << std::endl;
std::cout << "B引用计数: " << b.use_count() << std::endl;
// 尝试通过weak_ptr访问A对象
if (auto a_tmp = b.a_ptr.lock()) {
std::cout << "成功通过weak_ptr访问到A对象" << std::endl;
} else {
std::cout << "A对象已经被销毁" << std::endl;
}
// main函数结束时,a先析构,A的引用计数降为0,A对象被销毁
// 然后b析构,B的引用计数降为0,B对象被销毁
return 0;
}
运行修改后的代码,会输出A和B的析构信息,说明两个对象都被正确释放了,循环引用问题得到解决。
weak_ptr使用的注意事项
- weak_ptr不能单独使用,必须配合shared_ptr使用,它本身不能直接管理对象内存。
- 访问weak_ptr指向的对象前,一定要先调用
lock()方法判断对象是否还存在,避免访问已经释放的内存。 - 循环引用不是一定要用weak_ptr解决,也可以在设计上避免互相持有shared_ptr,但是weak_ptr是更通用、更优雅的解决方案。
- weak_ptr的
use_count()方法返回的是对应shared_ptr的引用计数,但是这个方法通常只用于调试,不建议在生产逻辑中使用。
总结
shared_ptr的循环引用问题本质是因为互相持有导致引用计数无法归零,而weak_ptr不增加引用计数的特性刚好可以打破这个循环。在实际开发中,当我们遇到对象之间可能存在互相引用的情况时,需要仔细分析对象的生命周期关系,合理使用weak_ptr来避免内存泄漏。正确使用智能指针可以让C++的内存管理更安全、更高效,减少手动管理内存带来的错误。
C++_weak_ptrC++_shared_ptr循环引用智能指针修改时间:2026-06-23 06:24:36