shared_ptr不是“自己写gc”的合理起点,因其已封装线程安全、弱引用解环、自定义删除器等关键机制;重复造轮子易引发悬垂指针或循环引用,且手动实现更慢更易错。

为什么 shared_ptr 不是“自己写 GC”的合理起点
直接手写引用计数型垃圾回收,很容易重复造轮子且引入悬垂指针或循环引用——shared_ptr 已经把线程安全、弱引用解环、自定义删除器这些关键点封装好了。你真正需要的不是“实现 GC”,而是理解它在什么场景下失效、怎么补救。
比如,用裸指针手动维护引用计数,operator new 和 delete 没对齐,或者忘记在拷贝构造里加计数,就会立刻崩溃。而 shared_ptr 把这些都收束在构造/赋值/析构三处,出错面小得多。
- 不要重写
shared_ptr的核心逻辑,它底层用原子操作保序,自己用int+mutex反而更慢、更易错 - 若需定制行为(如日志、内存池),优先通过自定义删除器传入,而不是改引用计数本身
- 循环引用不是“引用计数的缺陷”,而是对象图建模问题;该用
weak_ptr的地方硬用shared_ptr,等于主动制造泄漏
如何用 weak_ptr 安全打破循环引用
典型场景:父类持有一个 shared_ptr 指向子类,子类又通过 shared_ptr 持有父类——引用计数永远不归零,对象永不释放。
weak_ptr 不增加引用计数,只提供临时访问能力,且能检测对象是否还活着。关键不在“怎么声明”,而在“什么时候锁定、什么时候检查”。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 永远在使用前调用
lock(),得到一个临时shared_ptr;不能直接解引用weak_ptr - 如果
lock()返回空shared_ptr,说明目标已被释放,此时继续访问就是未定义行为 - 避免在析构函数中调用
lock()——对象正在销毁,生命周期已结束,weak_ptr无法“复活”它
class Parent;
class Child {
public:
std::weak_ptr<parent> parent_ref; // 不参与计数
void do_something() {
auto p = parent_ref.lock(); // 尝试获取强引用
if (p) {
p->notify();
} else {
// 父对象已销毁,跳过或报错
}
}
};</parent>
哪些情况必须放弃引用计数,改用其他策略
引用计数解决不了跨语言边界、非堆内存、或长生命周期缓存的管理问题。它假设“所有持有者都是 C++ 对象且遵守同一套规则”,现实往往不满足。
- 回调函数注册到 C 库(如 libuv、OpenGL)时,C 层不认
shared_ptr,必须用std::make_shared配合原始指针传递,并确保 C 层回调完成前 C++ 对象不销毁 - 对象被 mmap 映射或放在 arena 内存池里,
delete会崩;此时引用计数存在,但释放逻辑必须绕过operator delete - 缓存系统中,对象存活由 LRU 决定,而非谁还拿着指针——这时引用计数反而干扰淘汰逻辑,该用
std::unique_ptr+ 外部生命周期管理
调试循环引用泄漏的三个实际手段
编译期看不出循环引用,运行时表现就是进程 RSS 持续上涨、valgrind --leak-check=full 报“still reachable”但找不到源头。得靠工具链配合代码习惯定位。
- 给每个
shared_ptr构造/赋值点加日志(用宏控制),输出地址+计数变化,快速定位“谁多持有了” - 用
std::shared_ptr::use_count()在关键节点打点,但注意:它返回的是当前线程看到的近似值,多线程下仅作趋势参考 - 在类析构函数里加断点,看是否根本没被调用;如果是,再检查其成员里是否有
shared_ptr形成闭环,尤其是std::function捕获了this
真正麻烦的从来不是“怎么写引用计数”,而是确认所有持有路径都被覆盖、所有跨边界的生命周期契约都被显式约定。漏掉一个 C 回调里的裸指针,整个机制就失效。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










