发布时间:2026/7/31 4:01:47
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/7/31 3:56:47

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

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

2026/7/31 3:56:47

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

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

2026/7/31 7:01:55

AI教材编写工具:低查重与高质量内容实战指南

1. AI教材编写工具的核心需求解析在教育信息化浪潮中,AI辅助教材编写已成为教师和内容创作者的刚需。最近三个月内,某知识平台数据显示"AI教材工具"搜索量激增240%,但多数用户仍面临两个核心痛点:生成内容同质化严重导致…

2026/7/31 7:01:55

Unity DoTween DoMove失效全解析:从原理到实战排查指南

1. 项目概述:当DoTween的DoMove“罢工”时在Unity开发中,尤其是涉及到UI动画、角色移动或者场景物体平滑过渡时,DoTween几乎是每个开发者工具箱里的常客。它语法简洁,功能强大,能极大提升开发效率。然而,就…

2026/7/31 7:01:55

企业微信群机器人 Webhook 高效告警方案与限流应对策略

群机器人是研发运维团队监控告警落地的利器。通过简单的 Webhook 协议,系统可以把服务器异常、指标越界等信息实时推送到业务交流群。避坑指南与频率限制企业微信群机器人对单机器人发送频率有限制(通常为每个机器人每分钟不超过 20 条)。如果…

2026/7/31 7:01:55

大模型技术入门:从原理到应用落地

近几年,大模型成为人工智能领域最受关注的技术方向之一。从 ChatGPT 到国内各类大模型产品,从智能客服到代码生成,大模型正在快速进入真实业务场景。对于开发者来说,理解大模型不只是追热点,更是在理解未来软件开发的一…

2026/7/29 22:32:30

PDF合并与动态水印的工程化方案:2026国内免费工具实测对比

一、背景与测试方案 在实际项目交付中,PDF文件合并与版权保护水印的叠加是一个高频但容易被低估的技术需求。典型的处理链路涉及:多源PDF的文件流合并、页面级水印渲染(含透明度混合与图层叠加)、输出文件体积控制。看似简单的操作…

2026/7/31 0:01:11

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

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

2026/7/31 0:01:11

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

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

2026/7/31 0:01:11

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

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

2026/7/31 0:38:56

3个高效策略:快速掌握Axure中文界面配置

3个高效策略:快速掌握Axure中文界面配置 【免费下载链接】axure-cn Chinese language file for Axure RP. Axure RP 简体中文语言包。支持 Axure 11、10、9。不定期更新。 项目地址: https://gitcode.com/gh_mirrors/ax/axure-cn 还在为Axure RP的英文界面感…