当前位置: 首页
编程语言
C++实现简单的垃圾回收RAII方案 _ 计数指针原理【源码】

C++实现简单的垃圾回收RAII方案 _ 计数指针原理【源码】

热心网友 时间:2026-05-06
转载

C++ 实现智能指针:基于引用计数的 RAII 垃圾回收方案【完整源码解析】

C++实现简单的垃圾回收RAII方案 _ 计数指针原理【源码】

免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈

原生指针的缺陷:为何手动管理内存易导致双重释放与悬垂指针

在 C++ 中直接使用原始指针(raw pointer)配合 delete 进行内存管理,本质上是将“资源由谁释放”以及“何时释放”的决策责任完全交由开发者承担。当对象被多个模块共享,或程序因异常提前退出作用域时,关键的释放操作极易被遗漏、重复执行,甚至在对象已销毁后仍被访问,从而引发内存泄漏、双重释放(double-free)或悬垂指针(dangling pointer)问题。需要明确的是:RAII(资源获取即初始化)机制本身主要解决的是单作用域内的资源自动释放,而像 std::shared_ptr 这类基于引用计数的智能指针,才是对 RAII 理念的扩展——它将“引用计数”这一状态本身也纳入了资源管理的范畴。

在实际编程中,以下几类错误尤为常见:

  • 函数返回了在局部通过 new 分配的对象指针,而调用方忘记调用 delete,导致内存泄漏。
  • 两个 std::shared_ptr 虽然指向同一块原始内存,但却分别通过独立的 new 操作构造(非同一来源),导致引用计数信息错乱,最终引发双重释放。
  • 循环引用问题,例如对象 A 持有对象 B 的 std::shared_ptr,同时对象 B 也持有对象 A 的 std::shared_ptr,致使引用计数永不为零,对象无法被正确析构。

动手实现一个基础版 ref_ptr:非线程安全的引用计数指针

要实现一个最小可用的引用计数智能指针,其核心离不开三个要素:指向目标对象的原始指针、一个独立的引用计数器、以及封装在析构函数中的资源释放逻辑。这里有几点关键设计原则:计数器不应放在被管理对象内部(那属于侵入式方案,通用性差),也不能与对象共用同一块内存(除非定制 operator new),否则在 delete 对象时,计数器自身也将无法被安全释放。

具体实现时,需关注以下要点:

立即学习“C++免费学习笔记(深入)”;

  • 计数器需动态分配:应使用 new size_t(1) 来创建计数器,确保其生命周期独立于所托管的对象。
  • 正确实现拷贝与赋值:执行拷贝构造或赋值操作时,需先对原指针的计数器执行 --(*cnt) 并检查是否归零,以决定是否释放旧资源,然后再关联新资源并增加其计数。
  • 移动语义需妥善处理:实现移动构造函数和移动赋值运算符时,必须将源对象(右值)的内部指针和计数器置为 nullptr,防止其析构函数误释放资源。
  • 保持接口行为一致性:应提供 get()operator->operator* 等接口,其行为需与 std::shared_ptr 保持一致,以降低使用者的学习成本。

以下是一个简化版的核心实现代码示例:

template
class 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_ptrstd::shared_ptr 在 reset 和 release 上的差异

标准库中的 std::shared_ptr::reset() 设计得非常周全:它会先减少当前托管资源的引用计数,并据此判断是否需要释放,然后再去接管新的对象。而手写版本如果未实现类似逻辑,直接进行类似 ptr_ = new_obj; cnt_ = new_cnt; 的赋值,就会跳过对旧资源的清理步骤,从而导致内存泄漏。

在实现这些功能时,开发者常会遇到以下几个陷阱:

  • 误用赋值替代 reset():若未实现专门的 reset() 方法,而试图用拷贝赋值来替代,可能会引发不必要的临时对象构造与析构,不仅影响性能,还会使引用计数的管理逻辑变得异常复杂。
  • 实现 reset(nullptr) 时遗漏计数器置空:这会导致后续析构函数仍尝试对已置空的 cnt_ 进行 delete 操作,从而访问野指针,引发未定义行为。
  • 错误地提供 release() 方法:对于引用计数型智能指针,其语义并不支持“交出所有权但不减少引用计数”的操作,这会破坏 RAII 自动管理生命周期的核心契约,因此通常不应提供此方法。
  • 缺乏自定义删除器支持:简易实现往往不支持传入自定义删除器(Deleter),因此无法灵活管理那些非 new 分配(如 malloc)或需要特殊释放函数(如 CloseHandle)的资源。

破解循环引用:必须依赖 std::weak_ptr 吗?手写弱引用实现思路

弱引用指针(weak_ptr)并非另一个拥有所有权的指针,它本质上是对同一控制块(包含引用计数器)的**非拥有式观察者**。它不参与强引用计数的增减,仅在需要时尝试“升级”为强引用(即检查目标对象是否依然存活)。因此,要实现弱引用,必须在控制块中额外维护一个“弱引用计数”字段,用以记录当前有多少个 weak_ptr 正在观察此控制块。

若要手动实现弱引用功能,必须把握以下几个核心要点:

  • 扩展控制块结构:引用计数器需从单一的 size_t 扩展为至少包含 strong_count(强引用计数)和 weak_count(弱引用计数)两个字段的结构体。
  • 分离析构逻辑weak_ptr 析构时,只减少 weak_count;只有当 strong_count == 0weak_count == 0 两个条件同时满足时,才能安全地释放控制块内存本身。
  • 安全实现“升级”操作lock() 方法需要原子性地读取 strong_count 并判断是否大于 0。只有在对象存活时,才构造一个新的强引用指针(此时会增加 strong_count)。
  • 明确并发限制:即使实现的是非线程安全版本,也必须在文档或代码中明确标注“不保证多线程并发调用的安全性”,防止使用者误将其用于异步场景,导致难以调试的竞态条件问题。

实现到此阶段,其复杂度已接近 std::shared_ptr 标准实现的下限。对于绝大多数生产级项目而言,直接使用 C++ 标准库提供的智能指针是更稳健、高效的选择。自己动手实现引用计数指针,其主要价值在于深入理解其底层原理与设计思想,或用于某些无法使用标准模板库(STL)的特定受限环境,而非为了替代标准库。

来源:https://www.php.cn/faq/2313308.html

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
怎么利用 System.err 输出错误流并在控制台中以醒目的颜色标记(取决于终端)

怎么利用 System.err 输出错误流并在控制台中以醒目的颜色标记(取决于终端)

怎么利用 System err 输出错误流并在控制台中以醒目的颜色标记(取决于终端) System err 默认行为不带颜色,终端是否显示颜色取决于自身支持 首先得明确一点:System err 本质上只是 Ja va 标准库里的一个 PrintStream 对象。它本身并不负责“颜色”这种花哨的玩

时间:2026-05-06 09:59
如何在 Java 中使用 ThreadLocal.remove() 确保在线程池复用场景下不会发生数据污染

如何在 Java 中使用 ThreadLocal.remove() 确保在线程池复用场景下不会发生数据污染

如何在 Ja va 中使用 ThreadLocal remove() 确保在线程池复用场景下不会发生数据污染 说到线程池和 ThreadLocal 的搭配使用,一个看似不起眼、实则极易“踩坑”的细节就是数据清理。想象一下,你精心设计的线程池正在高效运转,却因为某个任务留下的“数据尾巴”,导致后续任务

时间:2026-05-06 09:59
怎么利用 Arrays.asList() 转换出的“受限列表”理解其对 add() 等修改操作的限制

怎么利用 Arrays.asList() 转换出的“受限列表”理解其对 add() 等修改操作的限制

Arrays asList():一个“受限”但实用的列表视图 在Ja va开发中,Arrays asList()是一个高频使用的方法,但你是否真正了解它返回的是什么?一个常见的误解是,它直接生成了一个标准的ArrayList。事实并非如此。 简单来说,Arrays asList()返回的并非我们熟悉

时间:2026-05-06 09:59
如何在 Java 中利用 try-catch 实现对“软错误”的平滑感知与非侵入式监控日志记录

如何在 Java 中利用 try-catch 实现对“软错误”的平滑感知与非侵入式监控日志记录

如何在 Ja va 中利用 try-catch 实现对“软错误”的平滑感知与非侵入式监控日志记录 在 Ja va 开发中,我们常常会遇到一些“软错误”——它们不会让程序直接崩溃,却可能悄悄影响业务的正确性或用户体验。比如,调用第三方 API 时返回了空响应、缓存查询未命中、配置文件里某个非关键项缺失

时间:2026-05-06 09:59
Django怎么防止Celery任务重复执行_Python结合Redis实现分布式锁

Django怎么防止Celery任务重复执行_Python结合Redis实现分布式锁

Django怎么防止Celery任务重复执行:Python结合Redis实现分布式锁 你遇到过吗?明明只发了一次任务,后台却执行了两次。这不是代码写错了,而是分布式环境下一个经典的老朋友:多个worker同时抢到了同一个活儿。 为什么Celery任务会重复执行 问题的根源在于竞争。想象一下,多个Ce

时间:2026-05-06 09:58
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 日榜
  • 周榜
  • 月榜
热门教程
更多
  • 游戏攻略
  • 安卓教程
  • 苹果教程
  • 电脑教程