c++中shared_ptr因引用计数机制易导致循环引用,需用weak_ptr打破并显式lock()检查;替代方案包括原始指针、unique_ptr单向拥有或对象池。

C++ 本身没有内置垃圾回收器,也不支持自动处理循环引用——这是你必须手动解决的问题,不是调用某个函数就能绕过的。
为什么 C++ 的 shared_ptr 会卡在循环引用里
shared_ptr 靠引用计数生存,两个对象互相持有对方的 shared_ptr,计数永远不降到 0,内存就永远不释放。这不是 bug,是设计使然。
典型场景:树节点父子双向引用、观察者模式中 subject 和 observer 互存、图结构中边与节点交叉持有。
- 只要存在
a->child = b且b->parent = a这类赋值,又都用shared_ptr,就已埋雷 - 运行时几乎不报错,只表现为内存缓慢上涨、
valgrind显示“still reachable”块持续增加 -
weak_ptr不增加引用计数,但它不是“自动 GC”,只是帮你打破计数环——你得自己决定哪一端该弱化
用 weak_ptr 打破循环的具体写法
关键不是“加 weak_ptr”,而是选对方向:把非拥有关系(non-owning)那一端改成 weak_ptr,并确保访问前先 lock()。
struct Node {
std::shared_ptr<node> parent;
std::vector<:shared_ptr>> children;
};
// ❌ 错误:父子双向 shared_ptr → 循环引用
// ✅ 正确:parent 改为 weak_ptr,children 保持 shared_ptr
struct Node {
std::weak_ptr<node> parent; // 不参与所有权
std::vector<:shared_ptr>> children;
};
</:shared_ptr></node></:shared_ptr></node>
- 访问
parent前必须调用parent.lock(),返回shared_ptr或空指针——这步不能省,否则解引用weak_ptr是未定义行为 - 不要在构造函数里直接把
this赋给weak_ptr;要用shared_from_this(),且类需继承std::enable_shared_from_this<node></node> -
weak_ptr本身不引发异常,但lock()返回空时若直接使用,会导致空指针解引用
替代方案:不用智能指针管理所有权
如果循环引用频繁、控制流复杂,硬靠 weak_ptr 容易漏判或误判,不如换更明确的所有权模型。
- 用原始指针(
Node*)或索引(如size_t)表示非拥有关系,彻底避开引用计数 - 改用
unique_ptr+ 移动语义,强制单向拥有(例如只允许父拥有子,子绝不持有父) - 引入 arena allocator 或对象池(如
boost::pool),整批分配/销毁,绕过逐个回收逻辑 - 第三方库如
libgc(Boehm GC)可嵌入 C++,但它不识别shared_ptr,需手动标记根集,且无法与 RAII 完全兼容
循环引用不是语法错误,它藏在设计决策里。最常被忽略的是:你以为加了 weak_ptr 就安全了,却忘了每次访问都要 lock() 并检查有效性——那行检查代码,比声明 weak_ptr 本身重要十倍。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











