shared_ptr通过引用计数机制管理对象生命周期,每个shared_ptr共享一个控制块,其中记录强引用计数,当强引用计数为0时自动释放资源;循环引用问题发生在多个对象相互以shared_ptr持有对方,导致引用计数无法归零,内存无法释放,例如父子节点间双向强引用;解决方法是将一方改为使用weak_ptr,weak_ptr不增加强引用计数,仅作为观察者,通过lock()安全访问对象,从而打破循环,确保内存正确释放。

shared_ptr的引用计数机制和循环引用问题是 C++ 智能指针使用中的核心知识点,理解它们对避免内存泄漏至关重要。
shared_ptr 引用计数是怎么工作的
shared_ptr通过引用计数来管理动态对象的生命周期。每个
shared_ptr实例指向一个资源(堆上分配的对象),同时共享一个控制块(control block),这个控制块中保存了:
- 指向实际对象的指针
- 当前有多少个
shared_ptr
共享该对象(强引用计数) - 当前有多少个
weak_ptr
观察该对象(弱引用计数) - 可能还包括自定义删除器、分配器等信息
每当一个新的
shared_ptr被创建并指向同一个对象时,强引用计数加 1;当一个
shared_ptr被销毁或重新赋值时,强引用计数减 1。当强引用计数变为 0 时,说明没有
shared_ptr再持有该对象,此时会自动调用删除器释放对象内存。
举个简单例子:
std::shared_ptrp1 = std::make_shared (42); std::shared_ptr p2 = p1; // 引用计数变为 2 p1.reset(); // 引用计数减为 1,对象未释放 // p2 仍然有效,直到它也被销毁
引用计数的操作是线程安全的(原子操作),多个线程同时增加或减少引用不会导致数据竞争。
循环引用问题是怎么产生的
循环引用发生在两个或多个对象通过
shared_ptr相互持有对方,导致引用计数永远无法归零,从而造成内存泄漏。
典型场景是父子节点结构中,父节点用
shared_ptr持有子节点,子节点也用
shared_ptr持有父节点:
在线证件照系统是一套完善的冲印行业解决方案,致力于解决用户线上拍摄证件照,拍摄最美最标准证件照的使命。证件照免费版功能:后台统计:当天制作、当天新增、支持规格、近7日统计规格列表:筛选查看、编辑用户列表:筛选查看常见问题:筛选查看、新增、编辑、删除小程序设置:应用设置、流量主设置小程序跳转:筛选查看、新增、编辑、删除关注公众号:引导设置系统要求:系统:Linux系统(centos x64)运行环境
struct Node;
struct Node {
std::shared_ptr parent;
std::shared_ptr child;
};
auto node1 = std::make_shared();
auto node2 = std::make_shared();
node1->child = node2;
node2->parent = node1; // 形成循环引用 此时:
node1
的引用计数为 2(外部指针 + node2 的 parent)node2
的引用计数为 2(外部指针 + node1 的 child)
当
node1和
node2离开作用域后,外部引用消失,引用计数各减为 1,但由于彼此还持有对方,计数不为 0,对象不会被销毁,造成内存泄漏。
解决循环引用的方案:使用 weak_ptr
最标准的解决方案是:把循环中的一方改为使用 weak_ptr
。
weak_ptr是一种不增加引用计数的观察者指针,它指向
shared_ptr管理的对象,但不影响其生命周期。只有在需要访问对象时,才通过
lock()方法尝试获取一个临时的
shared_ptr。
修改上面的例子:
struct Node {
std::shared_ptr child;
std::weak_ptr parent; // 改为 weak_ptr
};
auto node1 = std::make_shared();
auto node2 = std::make_shared();
node1->child = node2;
node2->parent = node1; // weak_ptr 不增加引用计数
// 访问父节点时:
if (auto parent = node2->parent.lock()) {
// parent 是临时的 shared_ptr,安全访问
std::cout << "Has parent\n";
} else {
std::cout << "Parent is gone\n";
} 这样:
node1
持有node2
(强引用)node2
只“观察”node1
(弱引用)- 当外部指针释放后,
node2
的引用计数变为 0,被销毁 - 接着
node1
的引用计数也变为 0,被销毁 - 内存正确释放,无泄漏
使用建议和注意事项
-
谁该用 weak_ptr? 通常“从属”或“反向”引用的一方使用
weak_ptr
。比如父持有子用shared_ptr
,子指向父用weak_ptr
。 weak_ptr
不能直接解引用,必须通过lock()
获取shared_ptr
。lock()
返回一个shared_ptr
,如果原对象已释放,则返回空shared_ptr
。weak_ptr
本身也有引用计数(弱引用计数),用于管理控制块的销毁时机。- 避免在
weak_ptr
上做expired()
判断后再lock()
,因为可能有竞态条件,直接lock()
更安全。
基本上就这些。关键点是:shared_ptr 靠引用计数自动管理生命周期,但循环引用会打破这个机制,用 weak_ptr 打破循环是最标准做法。









