news 2026/2/16 10:54:21

int * 代替外部计数结构体实现SharedPtr

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
int * 代替外部计数结构体实现SharedPtr

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

    • 一、核心矛盾点
    • 二、错误实现演示与问题分析
      • 错误代码片段
      • 测试场景与崩溃后果
      • 核心问题总结
    • 三、正确方案的本质:用指针共享堆上的计数
      • 简化优化:不用结构体,只用 `int*`
    • 四、补充解答:结构体是必须的吗?
    • 五、总结

我有一个疑问RefCount必须以一个类或者结构体放置在外面吗,这里面本质不就是一个计数吗,能不能把这个计算int类型变量直接放置在sharedptr类中作为成员变量呢,请说明行或者不行及其原因

结论先行:绝对不行,不能将引用计数直接作为SharedPtr的普通int成员变量,这违背了shared_ptr共享计数的核心设计原理。下面结合底层逻辑、错误案例、后果分析,为你详细解释原因,同时给出简化的正确实现方案。


一、核心矛盾点

shared_ptr的设计目标:多个智能指针对象共享同一个资源,且共用同一个引用计数,计数的增减对所有共享该资源的指针都生效。

  • 类的普通成员变量(值类型):属于对象独占,每个SharedPtr实例都会独立拥有一份成员变量副本,彼此隔离、无法同步;
  • 引用计数的需求:必须是全局唯一、所有共享实例都能访问修改的共享变量。

这是直接用int成员变量无法解决的本质矛盾。


二、错误实现演示与问题分析

我们模拟你设想的写法,把计数直接作为成员变量,看看会发生什么致命问题:

错误代码片段

template<typenameT>classBadSharedPtr{private:T*ptr_;intref_count_;// 直接将计数作为普通成员变量(错误写法)public:// 构造函数explicitBadSharedPtr(T*p=nullptr):ptr_(p),ref_count_(0){if(ptr_)ref_count_=1;}// 拷贝构造:拷贝指针和计数值BadSharedPtr(constBadSharedPtr&other){ptr_=other.ptr_;ref_count_=other.ref_count_;ref_count_++;// 尝试给当前对象的计数+1}// 析构函数~BadSharedPtr(){ref_count_--;if(ref_count_==0){deleteptr_;std::cout<<"资源释放\n";}}T&operator*(){return*ptr_;}intuse_count()const{returnref_count_;}};

测试场景与崩溃后果

voidtest(){BadSharedPtr<int>sp1(newint(10));// sp1.ptr_ → 堆内存,sp1.ref_count_ = 1BadSharedPtr<int>sp2=sp1;// 拷贝构造:sp2.ptr_ 和 sp1 指向同一块内存// sp2.ref_count_ = 2,sp1.ref_count_ 仍然是 1(两个独立变量!)std::cout<<sp1.use_count();// 输出 1std::cout<<sp2.use_count();// 输出 2}// 函数退出,析构顺序:sp2 先析构 → sp2.ref_count=1,不释放资源// sp1 后析构 → sp1.ref_count=0,释放资源 ✔️ 看似没问题?// 进阶场景:多一层拷贝BadSharedPtr<int>sp3=sp2;// 析构时:sp3计数减到1、sp2计数减到1、sp1计数减到0 → 仅释放一次// 但如果执行 reset、赋值操作,会直接崩溃:sp1.reset();// sp1 计数减为0,释放内存;但 sp2/sp3 仍持有该内存指针,变成野指针!// sp2 析构时会再次 delete 同一块内存 → **重复释放,程序崩溃**

核心问题总结

  1. 计数隔离,无法同步:每个对象的ref_count_都是独立副本,修改一个不会影响其他对象,计数彻底失效;
  2. 重复释放/内存泄漏:无法准确判断资源的真实引用数量,要么重复释放内存导致崩溃,要么计数永远不为0导致内存泄漏;
  3. 行为不可控:赋值、reset、移动等操作都会产生未定义行为。

三、正确方案的本质:用指针共享堆上的计数

要实现唯一且共享的计数,必须将计数对象分配在堆内存上,所有SharedPtr实例只存储一个指针,指向这块堆内存。
所有实例通过指针访问同一个计数变量,修改后全局同步,这也是我们最初用RefCount结构体/int*的核心原因。

简化优化:不用结构体,只用int*

你之前看到的RefCount结构体是为了扩展(兼容弱引用计数),如果仅实现基础共享计数,完全可以去掉结构体,直接用int*作为成员变量,这是最精简的正确写法:

#include<iostream>#include<utility>#include<string>template<typenameT>classSharedPtr{private:T*ptr_;// 托管资源指针int*ref_cnt_;// 指针:指向堆上的唯一引用计数(核心修正)// 私有释放逻辑voidrelease(){if(ref_cnt_){--(*ref_cnt_);// 计数为0,释放资源和计数对象if(*ref_cnt_==0){deleteptr_;deleteref_cnt_;}// 清空当前对象指针ptr_=nullptr;ref_cnt_=nullptr;}}public:// 空构造SharedPtr():ptr_(nullptr),ref_cnt_(nullptr){}// 有参构造explicitSharedPtr(T*p):ptr_(p){if(ptr_){ref_cnt_=newint(1);// 堆上分配计数,初始值1}else{ref_cnt_=nullptr;}}// 拷贝构造:共享指针和计数,计数+1SharedPtr(constSharedPtr&other){ptr_=other.ptr_;ref_cnt_=other.ref_cnt_;// 共享同一个计数指针if(ref_cnt_){++(*ref_cnt_);}}// 移动构造:转移所有权,不修改计数SharedPtr(SharedPtr&&other)noexcept{ptr_=other.ptr_;ref_cnt_=other.ref_cnt_;other.ptr_=nullptr;other.ref_cnt_=nullptr;}// 拷贝赋值SharedPtr&operator=(constSharedPtr&other){if(this!=&other){release();ptr_=other.ptr_;ref_cnt_=other.ref_cnt_;if(ref_cnt_)++(*ref_cnt_);}return*this;}// 析构函数~SharedPtr(){release();}// 工具函数T&operator*()const{return*ptr_;}T*operator->()const{returnptr_;}intuse_count()const{returnref_cnt_?*ref_cnt_:0;}voidreset(T*p=nullptr){release();ptr_=p;if(ptr_)ref_cnt_=newint(1);}};

四、补充解答:结构体是必须的吗?

不是必须的!

  • 基础场景:用int*就足够实现共享计数;
  • 扩展场景:需要弱引用计数、自定义删除器、原子操作标记等附加信息时,用结构体封装多个变量更优雅(这也是标准库和我们最初代码用RefCount的原因);
  • 核心约束不变:计数必须存储在堆上,通过指针共享,不能是类的值类型成员

五、总结

  1. 不行的根本原因:普通int成员变量是对象独占的,无法让多个SharedPtr共享同一个计数,会导致计数失效、内存崩溃;
  2. 正确实现规则:引用计数必须分配在堆内存中,所有智能指针对象通过指针共享这块内存,保证计数全局同步;
  3. 简化写法:无需结构体,用int*即可实现基础功能,结构体仅用于代码扩展和规范化管理。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/7 22:45:23

Libvio.link爬虫技术解析

文章目录Libvio.link爬虫技术解析一、网站结构与核心特征1.1 页面类型与数据分布1.2 技术特征分析二、反爬机制深度剖析2.1 基础反爬措施&#xff08;部分存在&#xff09;2.2 反爬强度评估三、爬虫核心技术实现方案3.1 基础爬取&#xff1a;静态内容获取3.2 动态内容处理方案方…

作者头像 李华
网站建设 2026/2/8 5:49:55

重启MySQL或者MariaDB服务

文章目录1. 编辑 MariaDB 配置文件2. 在配置文件中添加或修改相关设置3. 保存并重启 MariaDB 服务4. 验证二进制日志是否已启用总结原因根据您的输出&#xff0c; log_bin 显示为 OFF&#xff0c;这意味着二进制日志功能目前没有启用。您尝试使用 SET GLOBAL log_binon; 来启…

作者头像 李华
网站建设 2026/2/7 22:33:53

基于STM32的仓库温湿度数据获取系统

基于STM32的仓库温湿度数据获取系统 第一章 绪论 传统仓库温湿度管理依赖人工手持仪器巡检&#xff0c;存在数据采集频率低、人工成本高、数据易遗漏篡改、无法实时掌握全域温湿度状态等问题&#xff0c;尤其在大型仓储场景中&#xff0c;易因局部温湿度超标导致货物霉变、失效…

作者头像 李华
网站建设 2026/2/7 16:06:11

基于STM32的仓库环境监测系统的设计与实现

基于STM32的仓库环境监测系统的设计与实现 第一章 绪论 传统仓库环境监测多依赖人工巡检&#xff0c;存在数据实时性差、监测维度单一、异常预警滞后等问题&#xff0c;易因温湿度超标、有害气体泄漏、火灾隐患等导致货物损毁&#xff0c;难以满足现代化仓储的精细化管理需求…

作者头像 李华
网站建设 2026/2/13 19:55:05

Java扫码点餐系统:国际支付与多语言新突破

若要通过Java源码打造国际版扫码点餐系统&#xff0c;实现国际支付与多语言的新突破&#xff0c;可从以下技术架构和功能设计入手&#xff0c;结合实际业务需求进行开发或优化&#xff1a;一、技术架构&#xff1a;支撑全球化高并发与多语言适配后端框架Spring Boot 3.x Sprin…

作者头像 李华
网站建设 2026/2/3 14:52:30

同城自助KTV新体验:Java预约系统源码详解

同城自助KTV新体验&#xff1a;Java预约系统源码详解一、技术架构&#xff1a;微服务与边缘计算构建弹性底座微服务拆分与通信系统采用Spring Cloud框架&#xff0c;将核心功能拆分为用户服务、订单服务、设备服务、支付服务等独立模块。各服务通过RESTful API或Kafka消息队列实…

作者头像 李华