C++标准不提供内置垃圾回收,裸指针无法实现安全GC,因其不携带所有权信息、不跟踪生命周期,易导致double free、悬空指针等未定义行为;必须借助智能指针或自定义句柄封装引用计数与销毁逻辑。

C++ 标准不提供内置垃圾回收(GC),new 和 delete 必须手动配对;所谓“自定义垃圾回收”,本质是用指针封装 + 引用计数 / 对象生命周期策略,模拟 GC 行为——但无法真正替代 GC,也不能自动回收循环引用或栈对象。
为什么不能直接用 raw pointer 实现安全的 GC
裸指针 int* 不携带所有权信息,编译器和运行时均不跟踪其生命周期。常见误用包括:
- 多次
delete同一地址 →double free崩溃 -
delete后继续解引用 → 未定义行为(UB) - 指针复制后无共享管理机制 → 一方
delete导致悬空指针
所以,任何“自定义 GC”都必须绕过裸指针直接操作,改用带控制逻辑的智能指针或自定义句柄类。
用 std::shared_ptr 模拟引用计数 GC
std::shared_ptr 是最接近“自动回收”的标准方案:它通过引用计数在最后一个副本析构时调用 delete。但它不是 GC——没有全局扫描、不处理循环引用、不延迟回收。
立即学习“C++免费学习笔记(深入)”;
实操要点:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 永远用
std::make_shared<t>()</t>构造,避免new+shared_ptr分离导致异常安全问题 - 循环引用必须打破:一方用
std::weak_ptr持有,访问前调用lock()判活 - 自定义删除器可接管回收逻辑,例如归还到内存池:
shared_ptr<t>(raw_ptr, [](T* p) { my_pool::free(p); })</t> - 注意开销:控制块堆分配、原子计数操作,高频短命对象下性能敏感
手写简易引用计数指针(仅用于教学/受限场景)
若需完全可控(如嵌入式无 STL),可实现最小化 MySharedPtr,但必须包含:
- 指向对象的
T*和指向控制块(含计数)的ControlBlock* - 拷贝构造/赋值时原子增计数;析构/赋值覆盖时原子减计数,为 0 时
delete对象 + 控制块 - 禁止从裸指针隐式构造(避免绕过计数),只允许
MySharedPtr<t>::make()</t>工厂函数
示例关键片段:
template<typename T>
class MySharedPtr {
T* ptr_;
ControlBlock* cb_;
<p>public:
static MySharedPtr make() {
auto mem = ::operator new(sizeof(T) + sizeof(ControlBlock));
auto cb = new (mem) ControlBlock(); // placement new
auto t = new (static_cast<char*>(mem) + sizeof(ControlBlock)) T();
return MySharedPtr(t, cb);
}
// ... dtor/copy/move 实现计数逻辑
};</p>注意:这不处理线程安全(除非用 std::atomic_int)、不兼容数组、不支持别名构造(aliasing constructor),生产环境请优先用 std::shared_ptr。
哪些情况“自定义 GC”反而更危险
试图用全局 map 记录所有 new 出来的地址并定期扫描回收?这类 DIY 方案几乎必然失败:
- 无法区分指针是否已
delete过(没有元信息)→ 重复释放 - 无法识别栈对象或静态对象地址 → 错误回收
- 多线程下 map 读写需锁,而
new/delete可能在任意线程触发 → 高概率死锁或崩溃 - RTTI 关闭时连类型都不可知,
delete会跳过虚析构 → 内存泄漏或 UB
真正需要 GC 的场景(如脚本引擎、长时间运行的服务端逻辑),应引入成熟方案(如 Boehm GC),而非在 C++ 堆上自行扫描。C++ 的哲学是“你明确知道谁拥有,就明确释放”——越想绕过这点,越容易掉进更深的坑里。

















