题目
智能指针与循环引用问题
信息
- 类型:问答
- 难度:⭐⭐
考点
智能指针使用,循环引用分析,内存泄漏排查
快速回答
当两个对象通过shared_ptr相互引用时会导致循环引用,使引用计数无法归零,从而引发内存泄漏。解决方案:
- 将其中一个指针改为
weak_ptr打破循环 - 使用
weak_ptr::lock()安全访问对象 - 避免在可能形成环状结构的场景滥用
shared_ptr
问题背景
在C++中,shared_ptr通过引用计数实现自动内存管理。但当两个对象相互持有对方的shared_ptr时,会形成循环引用,导致引用计数永不归零,对象无法被销毁。
原理说明
shared_ptr的引用计数机制:
- 每次复制
shared_ptr时引用计数+1 - 每次析构
shared_ptr时引用计数-1 - 当引用计数归零时销毁对象
代码示例
问题代码(内存泄漏)
#include <memory>
class B;
class A {
public:
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A destroyed\n"; }
};
class B {
public:
std::shared_ptr<A> a_ptr;
~B() { std::cout << "B destroyed\n"; }
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->b_ptr = b; // A引用B
b->a_ptr = a; // B引用A
// 退出作用域时A和B不会被销毁
return 0;
}解决方案代码
class B;
class A {
public:
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A destroyed\n"; }
};
class B {
public:
std::weak_ptr<A> a_ptr; // 关键修改:使用weak_ptr
~B() { std::cout << "B destroyed\n"; }
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a; // weak_ptr不增加引用计数
// 安全访问示例
if(auto ptr = b->a_ptr.lock()) {
ptr->do_something();
}
return 0; // 对象正常销毁
}最佳实践
- 所有权设计:明确对象间的所有权关系(独占/共享/弱引用)
- weak_ptr使用场景:
- 打破循环引用
- 缓存系统(不延长对象生命周期)
- 观察者模式中的观察者列表
- lock()检查:访问
weak_ptr前必须用lock()检查有效性
常见错误
- 直接解引用
weak_ptr(应使用lock()) - 在循环结构中误用
shared_ptr(如双向链表、树结构的父节点) - 忽略
lock()的返回值检查导致访问悬空指针
扩展知识
- enable_shared_from_this:在类的成员函数中获取当前对象的
shared_ptr - 引用计数开销:
shared_ptr的原子操作有性能代价,高频场景慎用 - 工具检测:使用Valgrind、AddressSanitizer检测内存泄漏
- 替代方案:
- 对于独占所有权使用
unique_ptr - 手动管理生命周期(需谨慎)
- 对于独占所有权使用