C++手写shared_ptr共享智能指针|原子引用计数、强弱引用控制块、赋值重载底层深度剖析

发布时间:2026/9/18 9:11:57

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共享资源、支持拷贝依靠引用计数管理生命周期。
延伸阅读

更多相关文章

2026/9/6 21:41:12

OpenClaw安装方法2026,最简单的部署方式推荐

折腾了半天才发现,OpenClaw安装其实没那么玄乎 说实话,我第一次接触OpenClaw的时候,真的被各路教程给整懵了。有的说要配复杂的环境变量,有的要改Nginx配置,还有的动不动就让你编译源码……我电脑小白一个&#xff0c…

2026/9/16 20:20:23

基于eNSP的企业级网络综合实验:从VLAN、OSPF到NAT与ACL全配置

1. 项目概述:从零到一构建企业级模拟网络最近在带新人,发现很多刚接触网络技术的朋友,一听到“综合网配置”就有点发怵,觉得这玩意儿肯定复杂得要命。其实,它更像是在玩一个高自由度的“数字乐高”——给你一堆虚拟的华…

2026/9/18 9:11:34

微信PC端本地语音备份实战:从SQLite分库到SILK转MP3

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/18 9:11:34

MCU与MPU在机器人端侧AI中的分层协同设计

1. 为什么“心脏”这个词用在MCU和MPU身上,反而容易误导工程师?很多人一看到“端侧AI的‘心脏’之争”,第一反应是:这得是个高大上的技术选型辩论,像CPU和GPU谁更适合训练大模型那样,需要拉出算力、带宽、功…

2026/9/18 9:11:34

人机交互实验数据采集平台选型指南:传感器、同步与架构全解析

去年为了搭建一套人机交互场景下的具身智能数据采集平台,我在选型上折腾了将近两个月。中途踩过的坑,说多不多,说少不少,但最让我印象深刻的不是某个传感器参数没配好,而是团队在“数据采集平台”这个名词上根本没对齐…

2026/9/18 9:11:34

AI驱动的SEO关键词优化与流量提升策略

1. 项目概述"AI助力下的SEO关键词优化策略提升网站流量技巧"这个主题探讨的是如何利用人工智能技术来优化网站的关键词策略,从而有效提升自然搜索流量。作为一名从业多年的数字营销专家,我发现传统SEO工作正面临两大挑战:一是关键词…

2026/9/18 9:06:33

OpenClaw开源工具:智能爬虫应对动态网页与反爬策略

1. 项目概述OpenClaw作为一款新兴的开源工具,近期在技术社区引发了广泛讨论。这款工具的核心定位是提供一套完整的自动化抓取解决方案,特别适合需要从复杂网页结构中提取数据的开发者。不同于传统爬虫工具,OpenClaw在设计之初就考虑了现代Web…

2026/9/16 12:52:37

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/18 0:01:09

Google Colab 实战:运行模型、数据加载与报错排查

1. 为什么我劝你先搞懂 Colab 的运行模型1.1 Colab 到底是什么,跟本地跑代码差在哪Google Colab 简单说就是一台跑在浏览器里的 Linux 虚拟机,你打开一个 Notebook,背后就连上了一台带 GPU 的远程机器。你在单元格里敲的每一行 Python&#x…

2026/9/18 0:01:09

C语言数据类型与表达式详解

1. C语言数据与数据类型概述在C语言编程中,数据是程序处理的核心对象。理解数据的分类和特性是掌握C语言的基础。C语言中的数据主要分为四大类:常量、变量、表达式和函数。这些数据类型构成了C语言程序的基本元素,每种类型都有其独特的特性和…

2026/9/18 0:01:09

SQL时间字段指定时间段查询:区间语义、索引与时区避坑

上周排查一个线上问题&#xff0c;用户反馈"昨天的订单一条都没查到"&#xff0c;但数据库里明明躺着两千多条。最后定位下来&#xff0c;不是数据丢了&#xff0c;也不是接口挂了&#xff0c;而是那个查询条件把时间段写成了> 2024-05-20 00:00:00 AND < 2024…

2026/9/16 22:55:57

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行&#xff0c;Type-C接口算是典型的“看着简单&#xff0c;做起来全坑”的东西。光引脚就24个&#xff0c;高低速信号、电源、控制线全部塞在一个小小的连接器里&#xff0c;如果PCB布局不做规划&#xff0c;打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/16 22:56:09

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/16 22:56:16

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码