您的位置:首页 >C++实现简单的垃圾回收RAII方案 _ 计数指针原理【源码】
发布于2026-05-03 阅读(0)
扫一扫,手机访问

说到底,裸指针手动管理生命周期,本质上就是把“谁该释放”和“何时释放”的决策权,完全交给了程序员。一旦对象被多个模块共享,或者程序执行中途因为异常跳出作用域,那个关键的delete就很容易被漏掉、被重复调用,甚至在对象早已销毁后,代码还在尝试解引用它。这里有个常见的误解:RAII本身并不解决共享所有权的问题,它只保证在单个作用域内资源能自动释放。而像std::shared_ptr这样的计数指针,才是对RAII理念的自然延伸——它把“引用计数”这个状态本身,也纳入了资源管理的范畴。
实际开发中,下面这些错误现象屡见不鲜:
new出来的对象指针,调用方却忘了delete,结果就是内存泄漏。std::shared_ptr虽然指向同一块原始内存,但却是分别用new构造的(非同源),导致引用计数错乱,最终引发double-free。std::shared_ptr,B也持有A的,导致引用计数永远无法归零,对象无法被析构。ref_ptr(非线程安全版)要实现一个最简可用的引用计数指针,核心离不开三要素:指向对象的原始指针、一个独立的引用计数器、以及封装在析构函数里的资源释放逻辑。这里有几个关键点需要注意:计数器不能放在对象内部(那是侵入式方案,不够通用),也不能和对象共用同一块内存(除非定制operator new),否则在delete对象时,计数器本身就无法被安全释放了。
具体操作上,有这么几个要点:
立即学习“C++免费学习笔记(深入)”;
new size_t(1)来确保它的生命周期独立于它所托管的对象。--(*cnt),然后检查计数是否归零,以此来决定是否需要delete托管对象和计数器本身。get()、operator->等接口时,其行为应当与std::shared_ptr保持一致,降低使用者的心智负担。下面是一个简化版的关键路径代码示例:
templateclass ref_ptr { T* ptr_; size_t* cnt_; public: explicit ref_ptr(T* p = nullptr) : ptr_(p), cnt_(p ? new size_t(1) : nullptr) {} ref_ptr(const ref_ptr& other) : ptr_(other.ptr_), cnt_(other.cnt_) { if (cnt_) ++(*cnt_); } ~ref_ptr() { if (cnt_ && --(*cnt_) == 0) { delete ptr_; delete cnt_; } } T* get() const { return ptr_; } T& operator*() const { return *ptr_; } T* operator->() const { return ptr_; } };
ref_ptr 和 std::shared_ptr 在 reset / release 行为上的关键差异标准库的std::shared_ptr::reset()设计得很周全:它会先减少旧资源的引用计数,然后再去接管新对象。而手写版本如果没实现类似的逻辑,直接进行ptr_ = new_obj; cnt_ = new_cnt;这样的赋值,就会跳过对旧资源的清理步骤,从而导致内存泄漏或悬垂指针。
在实现这些功能时,有几个坑特别容易踩到:
reset():如果没实现专门的reset()方法,靠拷贝赋值来替代,可能会触发不必要的拷贝构造和析构,不仅性能差,还会让引用计数的逻辑变得复杂和沉重。reset(nullptr)时忘记置空cnt_:这会导致后续析构函数仍然尝试去delete cnt_,访问野指针,引发未定义行为。release():对于计数指针来说,语义上并不支持“交出所有权但不减少引用计数”这种操作,这会破坏RAII的基本契约,所以通常不应该提供release()方法。Deleter),因此无法处理那些由malloc分配,或者需要调用CloseHandle等特定函数来释放的资源。std::weak_ptr 解?手写怎么加弱引用(weak_ptr)并不是“另一个拥有所有权的指针”,它本质上是对同一块控制块(包含引用计数器)的**非拥有式观察者**。它不参与强引用计数(cnt_)的增减,只在需要时尝试“升级”为强引用(即检查对象是否还存活)。因此,要实现弱引用,就必须在控制块里额外维护一个“弱计数”字段,用来记录有多少个weak_ptr正在观察这个计数器。
自己动手实现的话,必须把握住这几个要点:
size_t扩展为至少包含strong_count和weak_count两个字段的结构体。weak_ptr析构时,只减少weak_count;只有当strong_count == 0 && weak_count == 0两个条件同时满足时,才能安全地释放计数器内存本身。lock()方法需要原子性地读取strong_count并判断是否大于0,只有在对象存活时,才构造一个新的ref_ptr(此时才会增加strong_count)。走到这一步,其复杂度已经逼近std::shared_ptr实现的下限了。对于绝大多数项目而言,直接使用标准库是更稳健的选择。自己实现引用计数指针,通常目的仅限于深入理解其原理,或者是为了嵌入那些无法使用STL的受限环境,而不是为了替代标准库。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
9