c++标准不提供垃圾回收,引用计数(如std::shared_ptr)仅为raii式确定性释放,并非真正gc;它无法解决循环引用、无堆扫描能力、有性能开销且不管理非new内存或非内存资源。

C++ 标准本身不提供垃圾回收(GC),但可以用引用计数实现类 GC 的自动内存管理效果——关键在于:它不是真正的 GC,而是确定性、可预测的资源释放;用错场景反而会引发循环引用或性能问题。
为什么不能直接用 std::shared_ptr 当“垃圾回收器”
std::shared_ptr 是引用计数智能指针,但它不解决循环引用,也不扫描堆内存。它只是在引用数归零时立即调用 delete,属于 RAII 范畴,不是 GC。
- 循环引用时,两个对象互相持有
shared_ptr,引用计数永不为 0,内存泄漏 - 频繁拷贝
shared_ptr会带来原子操作开销(尤其在多线程下) - 无法回收原始裸指针、
malloc分配的内存、或非new构造的对象 - 不处理栈对象、全局对象、或文件句柄等非内存资源(除非你自定义 deleter)
手动实现轻量引用计数类的关键点
自己写一个最小可用的引用计数指针,能帮你理解底层逻辑,也便于定制行为(比如日志、调试计数、弱引用支持)。
template<typename T>
class RefCountPtr {
T* ptr_;
size_t* count_;
<pre class='brush:php;toolbar:false;'>void release() {
if (count_ && --(*count_) == 0) {
delete ptr_;
delete count_;
}
}public: explicit RefCountPtr(T* p = nullptr) : ptr(p), count(p ? new size_t(1) : nullptr) {}
RefCountPtr(const RefCountPtr& other) : ptr_(other.ptr_), count_(other.count_) {
if (count_) ++(*count_);
}
RefCountPtr& operator=(const RefCountPtr& other) {
if (this != &other) {
release();
ptr_ = other.ptr_;
count_ = other.count_;
if (count_) ++(*count_);
}
return *this;
}
~RefCountPtr() { release(); }
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }};
立即学习“C++免费学习笔记(深入)”;
- 必须用
size_t*单独分配计数器,不能和对象布局耦合(否则无法通用) - 拷贝构造/赋值中要判空,避免对
nullptr解引用或递增空指针 - 析构时只负责释放计数器和对象,不负责重置
ptr_(这是安全的,因对象已销毁) - 没实现
weak_ptr等价物,所以仍无法破循环引用
如何检测和打破循环引用
循环引用不是语法错误,运行期才暴露为内存泄漏。靠工具或设计约束来预防更实际。
- 用
std::weak_ptr替代部分shared_ptr:比如观察者模式中,被观察者持shared_ptr<observer></observer>,而观察者内部用weak_ptr<subject></subject>回调 - 静态分析:Clang 的
-Wlifetime可捕获部分悬垂引用,但对跨对象循环无能为力 - 运行时调试:重载
operator new+ 全局计数器,配合atexit()检查未释放对象数量 - 设计上规避:用 ID 或句柄(
int)代替裸指针/智能指针传递,由中心容器管理生命周期
什么场景下真该考虑外部 GC(如 Boehm GC)
只有当你明确需要“不可预测但自动”的回收语义,且能接受限制时,才引入第三方 GC。
- 嵌入脚本解释器(如用 C++ 实现 Lua host),需托管脚本对象生命周期
- 遗留代码大量使用
malloc/free,又不愿重构为 RAII - 算法原型阶段,优先保证逻辑正确,暂不优化内存路径
Boehm GC 是保守式 GC,它可能把栈上一个整数误认为指针,从而漏回收真实对象;它不调用析构函数,所以不能用于管理含资源(文件、锁、GPU buffer)的对象——这点常被忽略。










