C++手写shared_ptr共享智能指针|原子引用计数、强弱引用控制块、赋值重载底层深度剖析
本篇博客自底向上拆解Cshared_ptr共享智能指针核心底层原理。一、前置头文件配置#define _CRT_SECURE_NO_WARNINGS #includeiostream using namespace std; #includecstdlib #includeassert.h #includeatomic知识点解析atomic头文件提供原子类型用于实现线程安全的引用计数解决多线程竞争问题是shared_ptr支持多线程的核心。_CRT_SECURE_NO_WARNINGS屏蔽VS安全警告适配本地编译运行。其余头文件用于常规IO、断言、内存操作支撑整体代码运行。二、自定义Int实体测试类 资源载体//------------------------------------------------------- class Int { private: int value; public: Int(int x 0) :value(x) { cout create Int Object this value endl; } ~Int() { cout delete Int Object this value endl; value -1; } Int(const Int it) :value(it.value) { cout it copy Int Object this value endl; } Int(Int it) :value(it.value) { it.value -1; cout move Int Object this value endl; } Int operator(Int it) { if (this ! it) { this-value it.value; it.value -1; } } Int operator(const Int a) { if (this ! a) this-value a.value; cout this operator (const Int it) a endl; return *this; } void Print()const { cout value endl; } ostream operator (ostream os) const { os value; return os; } istream operator (istream in) { in this-value; return in; } void Set(int x) { value x; } int GetValue()const { return value; } int Value() { return value; } const int Value()const { return value; } Int operator() { this-value 1; return *this;// } Int operator(int) { Int tmp(*this); *this; return tmp; } operator int() const { return this-value; } Int* operator () { return this; } const Int* operator () const { return this; } };知识点解析该类作为智能指针的测试资源载体完整实现类的核心生命周期函数与运算符重载用于观测内存创建、拷贝、移动、销毁全过程。生命周期函数构造、析构、拷贝构造、移动构造、拷贝赋值、移动赋值全程打印日志直观观察智能指针资源管理逻辑。功能重载输入输出运算符、前置/后置、int隐式类型转换、取地址运算符完善类的读写与运算能力。提供读写接口Set/GetValue/Value()兼顾普通对象与常量对象的取值安全。移动语义特性资源转移后将原对象数值置-1标记资源转移完成避免资源混乱。三、通用仿函数删除器 单对象数组适配//------------------------------------------------------- //共享指针 // 一个堆区资源被多个指针所指向就叫做共享指针。 //对于移动语义只有对堆区资源有意义。 //基于共享所有权使用引用计数控制块管理资源对象的生命期。 //在容器中保存shared_ptr对象是安全的 templateclass _Ty struct default_deleter { void operator() (_Ty* _Ptr) const { delete _Ptr; } }; //------------------------------------------------------------------------------------------------- templateclass _Ty struct default_deleter_Ty[] { void operator() (_Ty* _Ptr) const { delete[]_Ptr; } };知识点解析共享指针核心定义一个堆区资源被多个指针指向即为共享指针移动语义仅对堆区资源有效栈对象无意义。shared_ptr核心原理基于共享所有权通过引用计数控制块统一管理资源生命周期可安全存储在容器中。删除器为一元仿函数通过模板特化适配两种内存释放规则普通对象使用delete数组对象使用delete[]杜绝内存释放出错。解耦设计将内存释放逻辑与智能指针主体分离灵活性更高支持自定义删除器扩展。四、强弱引用计数控制块 My_ReCount 线程安全核心templateclass _Ty class My_ReCount { public: //typedef _Ty elemt_type; using elemt_ref _Ty; //typedef _Ty* elemt_ptr; using pointer _Ty*; private: _Ty* _Ptr; std::atomicint _Uses; //shared_ptr,针对于共享引用的引用计数 std::atomicint _Weaks; //weak_ptr针对于弱引用的引用计数 /*定义了两个原子计数器用于实现C智能指针中的引用计数机制*/ public: My_ReCount(pointer psnullptr) :_Ptr(ps), _Uses(0), _Weaks(0) { if (nullptr ! ps)//如果没有地址我们不能指向它所以我们要去进行基本的判断。 { _Uses 1; } ~My_ReCount() default;//就并不是删除删除是delete void Incref() { void Incwef() { _Weaks 1; } --_Uses 0) { Decwref(); } return --_Weaks; } int getwref() c int _use_count()const { return _Uses.load(一个原子操作属于atomic头文件它安全地获取 _Uses 的当前值确保在多线程环境下获取到的值是一致的、未被修改的 } /*对于std::atomicint _Uses和std::atomi两个原子计数器它们的操作如 1或--都是原子的这意味着在多线程环境下这些操作不会被其他线程中断或干扰。 具体来说 完整执行不会被其他线程打断 _Weaks 1时这个操作也会完整执行不会被其他线程打断 这两个操作可以同时进不会互相干扰 原子操作保证了在多线程环境下引用计数的正确性避免了竞争条件(race conditio的智能指针如std::shared_ptr的关键机制之一。*/ };n)的问题。这是实现线程安全行但它们各自都是原子操作 同样当另一个线程执行 当一个线程执行_Uses 1时这个操作会cint _Weaks这); //_Uses.load() 是onst { return _Weaks; } return _Uses; } int Decwref() { int Decref() { if ( _Uses 1; }是什么都不做的析构函数这里 _Weaks 1; //后面解释,与循环引用有关系 }知识点解析核心成员维护两个原子计数器_Uses管理shared_ptr强引用计数_Weaks管理weak_ptr弱引用计数天然支持多线程安全。原子操作特性atomic类型的自增、自减、load操作都是原子性的不会被线程打断彻底解决多线程竞争条件问题。构造逻辑传入有效堆地址时默认将强弱引用计数都初始化为1为后续解决循环引用问题做铺垫。析构函数默认空控制块的销毁不由自身析构处理由上层shared_ptr统一判断释放职责分离。核心方法Incref()强引用计数1拷贝共享指针时调用Incwef()弱引用计数1适配弱指针场景Decref()强引用计数-1计数归0时自动递减弱引用_use_count()原子读取当前强引用数量保证读取数据准确。五、核心手写 my_shared_ptr 完整实现templateclass _Ty, class _Dx default_deleter_Ty class my_shared_ptr { public: using elemtype _Ty; using Deleter_type _Dx; using pointer _Ty*; private: _Ty* _mPtr;//heap;sys; My_ReCount_Ty* _mRep;//引用计数器 //面试问法为什么不是用值而用对象 //首先如果是用值的计数器的话很难保证时效性也就是技术块是各自的在假设已经有很多智能指针指向同一块内存空间的时候当其中一个智能指针不指向这块内存中时剩余的对象 //都要进行计数器的修改此时系统的开销是十分巨大的如果采用堆区的指针就可以公用一个计数器多个共享指针公用同一个计数器保证了时效性也保证了效率。 Deleter_type _mDeleter;//删除器 public: void reset(_Ty* ps) {//值重置。 if (_mPtr ! nullptr _mRep ! nullptr _mRep-Decref() 0) {//回收的前提是它要存在//首先得保证这个共享型智能指针必须有值没有值那不叫重置那叫做赋值另外要保证它的完整性要有计数器才能知道有几个指向这同一块内存 //另外如果除了它自己没有其他人共享指针指向这块内存的话直接将这个堆内存进行释放。 _mDeleter(_mPtr); if (_mRep-Decwref() 0) {//如果连弱引用都没有了那么计数器此时两个值都为0那么就没有存在的必要了此时直接将计数器的堆内存释放掉堆内存都已经没了。 delete _mRep; } } _mPtr nullptr;//指向也直接置为空 _mRepnullptr;//计数器也置为空。 //进行重置操作。 //空的不要 if (nullptr ! ps) { _mPtr ps; _mRep new My_ReCount_Ty(_mPtr);//重新分配一个计数器。 } } my_shared_ptr(pointer ps nullptr) : _mPtr(ps), _mRep(nullptr) {//地址的赋值已经在这里写了 if (nullptr ! _mPtr) { _mRep new My_ReCount_Ty(_mPtr);//这里是分配一个计数器 } } ~my_shared_ptr() { reset(nullptr); } //拷贝构造函数的重载 my_shared_ptr(const my_shared_ptr other) :_mPtr(other._mPtr), _mRep(other._mRep) { if (_mPtr ! nullptr) { _mRep-Incref();//因为这是拷贝构造函数它之前是不存在的肯定没有指向其他内存空间所以不用进行之前的减一操作只用进行加一操作。 } } my_shared_ptr(my_shared_ptr other): _mPtr(other._mPtr), _mRep(other._mRep) { //转移资源 other._mPtr nullptr; other._mRep nullptr; } void swap(my_shared_ptr other) { std::swap(_mPtr, other._mPtr); std::swap(_mRep, other._mRep); } //面试重点 //移动赋值以及赋值函数重载 //赋值重载函数为什么难是因为引用计数块或者说者两个对象对应的内存分别被其他多个对象分别指向也有可能都为空值情况非常非常多。 // 因为共享指针是不能这样子的Int * p new Int(10); my_shared_ptrInt pa(p); my_shared_ptrInt pb(p);此时pa和pb指向同一块内存但是这是两个计数器这两个计数器在堆上的地址是各不相同的。 // 这也是十分危险的操作我们默认是不可以进行这样操作的。 //正常情况下就不可能出现地址为空计数器不为空以及地址或者地址不为空计数器为空的情况只有两者都为空和两个不为空这两种状态。 my_shared_ptr operator(const my_shared_ptr other) { if (this ! other) { if (this-_mPtr nullptr other._mPtr ! nullptr) { this-_mPtr other._mPtr; this-_mRep other._mRep; this-_mRep-Incref(); } else if (other._mPtr ! nullptr) { //最常见的情况 my_shared_ptrInt(other).swap(*this); //copt加上swap的巧妙之处在于完美的利用了临时对象生命周期的特点先创建一个中间对象然后指向other,other引用计数加一此时这个临时对象自动调用了swap函数此时是临时对象和当前类进行进行 //资源的互换此后临时对象存储的是当前类之前的资源当前类指向的是other类对象然后程序结束后这个临时对象回收自动调用析构函数同时调用reset函数进行计数器减一操作 //通过这一行代码完美实现了交换资源加上对应计数器的加一减一操作十分的巧妙。 return *this; } } return *this; } my_shared_ptr operator(my_shared_ptr other) { //最常见的情况 //同样的道理只不过这里通过右值拷贝构造函数的特性先将other的资源转移到中间变量tmp暂时接管此时这个other变为计数器和指向地址都为空的中间状态此时这个中间变量调用 //swap函数与当前类进行资源互换此时这个other指向的资源一直处于转移状态所以并没有导致计数器发生变化但是此时当前对象的旧资源由tmp接管它析构的时候会自动将这个旧资源 //的计数器进行减一操作。 if (this ! other) { if (this-_mPtr nullptr other._mPtr ! nullptr) { swap(this-_mPtr, other._mPtr); swap(this-_mRep, other._mRep); } else if (other._mPtr ! nullptr) { my_shared_ptrInt(std::move(other)).swap(*this); } } return *this; } };核心知识点解析 含面试重点5.1 核心成员变量设计 面试高频_mPtr指向用户真实堆资源的指针_mRep堆上的引用计数控制块指针。面试题解答为什么计数器用堆对象指针不用栈值如果使用栈值计数器每个智能指针都有独立计数器多指针共享同一块内存时需要逐个修改计数器系统开销极大。 使用堆区控制块多个shared_ptr共享同一个计数器全局统一修改计数保证时效性与运行效率。_mDeleter通用删除器仿函数负责最终内存释放。5.2 reset重置函数核心逻辑重置前置条件智能指针必须持有有效资源、有效计数器才能执行资源回收。资源释放逻辑强引用计数归0 → 调用删除器释放用户堆内存 → 递减弱引用计数若弱引用也归0彻底销毁计数器控制块。重置后置操作将自身指针、计数器全部置空再绑定新的内存与计数器状态干净无残留。5.3 生命周期函数逻辑构造函数创建智能指针绑定堆资源自动创建对应的引用计数控制块。析构函数直接调用reset(nullptr)依托reset逻辑自动处理计数递减、资源释放、控制块销毁。拷贝构造共享资源与计数器仅执行强引用计数1无需释放任何资源。移动构造直接转移资源与计数器所有权原对象置空计数不变零开销转移。5.4 赋值重载核心难点 面试重中之重赋值重载难点两个智能指针可能各自被多个对象引用、可能为空场景极其复杂极易出现计数错乱、内存泄漏、重复释放。禁止危险写法裸指针多次初始化shared_ptr会创建多个独立计数器导致重复释放崩溃。拷贝赋值巧妙逻辑利用临时对象swap机制 1. 临时对象拷贝other资源计数1 2. 临时对象与当前对象交换资源 3. 临时对象生命周期结束析构自动释放当前对象旧资源、计数-1 一行代码完美完成新旧资源更替、计数增减无冗余逻辑。移动赋值巧妙逻辑利用move转移资源所有权通过临时对象接管旧资源析构自动回收旧资源保证计数准确性。六、多场景测试用例 全部保留原生代码6.1 主测试用例 默认执行int main() { my_shared_ptrInt pa,pb; pa pb; my_shared_ptr my_shared_ptrInt pd(new Int(100)); my_ pe(pd); pd pc; return 0; }shared_ptrIntInt pc(new Int(10));测试场景解析空指针赋值测试两个空shared_ptr互相赋值无资源操作安全无报错。多对象共享测试pd、pe共享同一块内存引用计数正常递增。资源替换赋值pd从指向100的对象赋值改为指向10的对象旧资源计数递减、新资源计数递增。6.2 拷贝移动复合测试用例 条件编译#if 0 int main() { my_shared_ptrInt pa(new Int(10)); my_shaInt pb(pa); my_shared_ptrInt pc(std::move(pa)); } #endif; return 0red_ptr测试场景解析pa初始化堆对象强引用计数1。pb拷贝构造pa共享资源强引用计数2。pc移动构造papa资源被转移置空计数不变仅所有权转移。6.3 局部作用域计数销毁测试用例 条件编译#if 0 int main() { my_shared_ptrInt pa(new Int(10)); my_shared_ptrInt pb(pa); { my_shared_ptr -----------------{}------------- endl; } #endif return 0; }Int pc(pb); cout 测试场景解析利用局部代码块作用域特性pc为局部对象出作用域自动析构。pc析构触发计数递减验证局部对象销毁不影响全局共享资源仅引用计数变更。直观体现shared_ptr基于计数的资源生命周期管理核心。七、全文核心总结shared_ptr核心本质共享所有权智能指针通过堆上原子引用计数控制块实现多指针共享同一块堆资源线程安全。计数设计精髓强弱双原子计数强引用管控资源生命周期弱引用管控控制块生命周期解决循环引用问题。资源释放规则强引用归0释放用户堆资源强弱引用全部归0释放计数控制块分层释放杜绝内存泄漏。赋值重载最优解临时对象swap机制极简代码完成资源更替、计数增减规避所有边界场景报错。与unique_ptr核心区别unique_ptr独占资源、禁止拷贝shared_ptr共享资源、支持拷贝依靠引用计数管理生命周期。

相关新闻

基于MOS管与运放构建理想二极管电路:实现高效防反接与防倒灌

基于MOS管与运放构建理想二极管电路:实现高效防反接与防倒灌

1. 项目缘起:为什么“理想二极管”是电源设计的刚需?在嵌入式硬件、便携设备或者多电源供电系统的开发中,电源接口的保护和电源路径的管理,是决定产品可靠性的第一道门槛。我见过太多因为电源反接、多电源冲突导致主控芯片烧毁、电…

2026/7/31 4:00:49 阅读更多 →
Windows安装配置CodeX

Windows安装配置CodeX

1、安装 微软商店先点击获取,安装号之后先不启动,先配置API Key2、安装cc-switch3、配置cc-switch以百炼为例(也可以用自己常用的平台):4、启动 可以先跳过切换CodeX成功运行

2026/7/31 4:00:49 阅读更多 →
计算机网络自顶向下方法:从HTTP到TCP/IP的工程实践指南

计算机网络自顶向下方法:从HTTP到TCP/IP的工程实践指南

在实际学习和工作中,计算机网络知识是软件工程师、系统运维和任何与互联网技术相关岗位的基石。无论是调试一个微服务间的调用超时,还是理解为什么浏览器会提示“存在异常流量”,其背后都是计算机网络协议在起作用。马萨诸塞大学采用的《Comp…

2026/7/31 4:00:49 阅读更多 →

最新新闻

Python排序文件按时间?这招绝了,别再傻傻手动翻

Python排序文件按时间?这招绝了,别再傻傻手动翻

其中一种高级解释型编程语言, 是全世界作编程工作的人员都在运用的。它最为知名的方面, 是面向对象编程。我们能够于跟人工智能、机器学习、Web开发以及数据分析相关联的各异IT领域当中加以运用, 它流行且实用的另外理由是其具备诸多内置的库还有模块。怎样对日期以及时间进行排…

2026/7/31 4:37:21 阅读更多 →
多博学DR.UNI留学申诉与学术辅导服务靠谱程度究竟如何?

多博学DR.UNI留学申诉与学术辅导服务靠谱程度究竟如何?

多博学DR.UNI留学申诉与学术辅导服务相对较为靠谱。多博学DR.UNI是一家专注于留学学术申诉与辅导领域的服务机构。本文将从专业实力、服务流程、用户反馈、行业对比几个维度展开分析。专业实力是否过硬?多博学DR.UNI具备较强的专业实力。该机构业务案例累计已达5万&…

2026/7/31 4:37:21 阅读更多 →
风险管理经典书籍推荐:从掌握方法到建立判断力

风险管理经典书籍推荐:从掌握方法到建立判断力

提到风险管理领域的经典书籍,《经理人参阅:风险管理》一直都有很高的认可度,也是很多管理者持续推荐的一本书。风险管理类图书这些年出了不少,新观点、新概念层出不穷,但能够经过时间检验、一直保持影响力的作品并不多…

2026/7/31 4:37:21 阅读更多 →
一个人也能扛起整个基础设施:一套 AI 手搓的运维管理平台

一个人也能扛起整个基础设施:一套 AI 手搓的运维管理平台

一、背景说明我们公司的 IT 基础设施并不算小:近百台服务器、多套数据库(MySQL / MariaDB / ClickHouse / Redis / MongoDB)、一堆交换机路由器等网络设备、分散在多个云厂商的账户与账单,还有域名、SSL 证书、CDN、ICP 备案这些&…

2026/7/31 4:37:21 阅读更多 →
15-消息网关(二)-企业级平台全覆盖

15-消息网关(二)-企业级平台全覆盖

15 消息网关(二)——企业级平台全覆盖 王涛是某互联网公司的技术负责人,他计划将Hermes引入团队日常工作流。但团队既有用企业微信的,也有用飞书的,还有用Slack的国际团队。能否让Hermes同时接入所有内部平台,让不同团队的成员都能用自己习惯的方式与Agent交互?答案是…

2026/7/31 4:37:21 阅读更多 →
PaddleOCR C++源码编译与部署实战:从环境搭建到性能调优

PaddleOCR C++源码编译与部署实战:从环境搭建到性能调优

1. 项目概述:为什么选择源码编译PaddleOCR C版本 如果你正在处理一个需要离线部署、对性能有极致要求,或者需要深度定制OCR识别流程的项目,那么直接使用PaddleOCR官方提供的Python轮子可能就不够用了。Python版本虽然方便,但在生…

2026/7/31 4:36:20 阅读更多 →

日新闻

物理复制比逻辑复制好在哪?数据库复制原理详解

物理复制比逻辑复制好在哪?数据库复制原理详解

数据库复制是把主库数据同步到备库的机制,分为逻辑复制和物理复制两种。逻辑复制传输的是 SQL 语句或行变更事件,物理复制传输的是存储引擎底层的物理日志。阿里云 PolarDB(云原生数据库)采用物理复制,在同步延迟、数据…

2026/7/31 0:00:34 阅读更多 →
BilibiliDown:3分钟学会B站视频下载的终极指南

BilibiliDown:3分钟学会B站视频下载的终极指南

BilibiliDown:3分钟学会B站视频下载的终极指南 【免费下载链接】BilibiliDown (GUI-多平台支持) B站 哔哩哔哩 视频下载器。支持稍后再看、收藏夹、UP主视频批量下载|Bilibili Video Downloader 😳 项目地址: https://gitcode.com/gh_mirrors/bi/Bilib…

2026/7/31 0:00:34 阅读更多 →
有哪些游戏数据AI平台?游戏行业Data+AI融合方案盘点

有哪些游戏数据AI平台?游戏行业Data+AI融合方案盘点

当前,游戏行业的“DataAI融合”已从概念验证进入价值落地阶段。根据IDC 2025年数据,中国AI游戏云市场规模已达18.6亿元;同时,游戏研发环节AI渗透率高达86%,生成式AI内容普及率超过50%。面对庞大的市场,游戏…

2026/7/31 0:00:34 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/31 1:03:03 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/29 14:34:28 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/31 4:19:39 阅读更多 →

月新闻