news 2026/5/1 10:46:14

手写一个智能指针:从 unique_ptr 到 shared_ptr 的引用计数原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手写一个智能指针:从 unique_ptr 到 shared_ptr 的引用计数原理

博主介绍:程序喵大人

  • 35 - 资深C/C++/Rust/Android/iOS客户端开发
  • 10年大厂工作经验
  • 嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手
  • 《C++20高级编程》《C++23高级编程》等多本书籍著译者
  • 更多原创精品文章,首发gzh,见文末
  • 👇👇记得订阅专栏,以防走丢👇👇
    😉C++基础系列专栏
    😃C语言基础系列专栏
    🤣C++大佬养成攻略专栏
    🤓C++训练营
    👉🏻个人网站

C++ 内存管理的噩梦始于 new/delete 的手动配对,止于智能指针的自动化革新。从 RAII(资源获取即初始化)的核心理念,到 unique_ptr 的独占所有权,再到 shared_ptr 的引用计数共享机制,智能指针体系不仅解决了内存泄漏和悬空指针的顽疾,更通过类型系统明确了资源所有权语义。本文将深入剖析智能指针的实现原理,手写核心代码,助你彻底掌握这一现代 C++ 基石技术。

一、RAII:资源管理的哲学基石

RAII(Resource Acquisition Is Initialization,资源获取即初始化)是 C++ 最重要的设计理念之一。其核心思想简单而强大:资源的生命周期与对象的生命周期绑定。在对象构造时获取资源,在对象析构时自动释放资源,利用 C++ 栈对象的自动析构机制确保资源正确清理。

classFileHandler{public:FileHandler(conststd::string&path){fileHandle=fopen(path.c_str(),"r");// 构造即获取资源}~FileHandler(){if(fileHandle)fclose(fileHandle);// 析构必释放资源}private:FILE*fileHandle;};voidprocessFile(){FileHandlerfile("data.txt");// 自动打开// 使用文件...}// 离开作用域,自动关闭,即使发生异常

RAII 的三大核心特性:
自动释放,告别手动 delete / close;
异常安全,即使发生异常也能正确回收资源;
禁止拷贝、支持移动,资源只能有唯一所有者。

这种设计让资源管理变得可预测、可维护,是智能指针诞生的思想基础。

二、unique_ptr:独占所有权的轻量级守护者

unique_ptr 体现了独占所有权的清晰语义。在任何时候,只有一个 unique_ptr 可以指向一个给定的对象。它通过禁止拷贝构造函数和拷贝赋值运算符,只提供移动构造函数和移动赋值运算符来实现所有权的唯一性。

手写 unique_ptr 的核心实现
template<typenameT>classMyUniquePtr{private:T*ptr_;public:MyUniquePtr():ptr_(nullptr){}explicitMyUniquePtr(T*ptr):ptr_(ptr){}MyUniquePtr(constMyUniquePtr&)=delete;MyUniquePtr&operator=(constMyUniquePtr&)=delete;MyUniquePtr(MyUniquePtr&&other)noexcept:ptr_(other.ptr_){other.ptr_=nullptr;}MyUniquePtr&operator=(MyUniquePtr&&other)noexcept{if(this!=&other){deleteptr_;ptr_=other.ptr_;other.ptr_=nullptr;}return*this;}~MyUniquePtr(){deleteptr_;}T&operator*()const{return*ptr_;}T*operator->()const{returnptr_;}T*get()const{returnptr_;}T*release(){T*temp=ptr_;ptr_=nullptr;returntemp;}voidreset(T*ptr=nullptr){deleteptr_;ptr_=ptr;}explicitoperatorbool()const{returnptr_!=nullptr;}};

unique_ptr 的核心设计原则:
零开销抽象,性能几乎等同于裸指针;
异常安全,所有权转移过程不抛异常;
类型安全,在编译期防止错误使用。

这也是为什么在现代 C++ 中,unique_ptr 是默认首选的智能指针。

三、shared_ptr:引用计数的共享所有权模型

shared_ptr 允许多个指针共享同一个对象,通过引用计数机制管理对象生命周期。当最后一个 shared_ptr 被销毁时,对象才会被删除。其核心是控制块(Control Block)的设计。

引用计数控制块的内存布局

控制块是一个堆上的独立内存区域,通常包含以下内容:

  • 强引用计数(use_count)
  • 弱引用计数(weak_count)
  • 指向被管理对象的指针
  • 删除器(deleter)
template<typenameT>classControlBlock{public:std::atomic<size_t>use_count{1};std::atomic<size_t>weak_count{0};T*ptr{nullptr};std::function<void(T*)>deleter;ControlBlock(T*p,conststd::function<void(T*)>&del):ptr(p),deleter(del?del:[](T*p){deletep;}){}};
手写 shared_ptr 的核心实现
template<typenameT>classMySharedPtr{private:T*ptr_;ControlBlock<T>*ctrl_block_;voidincrement_ref(){if(ctrl_block_){ctrl_block_->use_count.fetch_add(1,std::memory_order_relaxed);}}voiddecrement_ref(){if(ctrl_block_){if(ctrl_block_->use_count.fetch_sub(1,std::memory_order_acq_rel)==1){ctrl_block_->deleter(ctrl_block_->ptr);ctrl_block_->ptr=nullptr;if(ctrl_block_->weak_count.load(std::memory_order_acquire)==0){deletectrl_block_;}}}}public:MySharedPtr():ptr_(nullptr),ctrl_block_(nullptr){}explicitMySharedPtr(T*ptr):ptr_(ptr),ctrl_block_(newControlBlock<T>(ptr,nullptr)){}template<typenameDeleter>MySharedPtr(T*ptr,Deleter del):ptr_(ptr),ctrl_block_(newControlBlock<T>(ptr,del)){}MySharedPtr(constMySharedPtr&other):ptr_(other.ptr_),ctrl_block_(other.ctrl_block_){increment_ref();}MySharedPtr&operator=(constMySharedPtr&other){if(this!=&other){decrement_ref();ptr_=other.ptr_;ctrl_block_=other.ctrl_block_;increment_ref();}return*this;}MySharedPtr(MySharedPtr&&other)noexcept:ptr_(other.ptr_),ctrl_block_(other.ctrl_block_){other.ptr_=nullptr;other.ctrl_block_=nullptr;}~MySharedPtr(){decrement_ref();}T&operator*()const{return*ptr_;}T*operator->()const{returnptr_;}size_tuse_count()const{returnctrl_block_?ctrl_block_->use_count.load(std::memory_order_relaxed):0;}T*get()const{returnptr_;}voidreset(T*ptr=nullptr){decrement_ref();if(ptr){ptr_=ptr;ctrl_block_=newControlBlock<T>(ptr,nullptr);}else{ptr_=nullptr;ctrl_block_=nullptr;}}};

四、引用计数的线程安全性考量

shared_ptr 的引用计数在多线程环境下必须是线程安全的,标准库通过原子操作来保证这一点。

关键实现细节包括:

  1. 使用 fetch_add / fetch_sub 操作引用计数
  2. 增加引用时使用 memory_order_relaxed
  3. 减少引用并可能释放资源时使用 memory_order_acq_rel
ctrl_block_->use_count.fetch_add(1,std::memory_order_relaxed);if(ctrl_block_->use_count.fetch_sub(1,std::memory_order_acq_rel)==1){// 释放资源}

相比互斥锁,原子操作的性能开销要小得多,是 shared_ptr 设计中的关键优化点。

五、循环引用问题的产生与解决

当多个对象通过 shared_ptr 相互持有对方时,会形成循环引用,导致引用计数永远无法归零,从而引发内存泄漏。

典型的循环引用场景
classB;classA{public:std::shared_ptr<B>b_ptr;};classB{public:std::shared_ptr<A>a_ptr;};voidcircularReference(){autoa=std::make_shared<A>();autob=std::make_shared<B>();a->b_ptr=b;b->a_ptr=a;}
使用 weak_ptr 打破循环

weak_ptr 不增加强引用计数,只作为观察者存在。

classB;classA{public:std::shared_ptr<B>b_ptr;};classB{public:std::weak_ptr<A>a_ptr;};voidfixedCircularReference(){autoa=std::make_shared<A>();autob=std::make_shared<B>();a->b_ptr=b;b->a_ptr=a;if(autoa_locked=b->a_ptr.lock()){// 对象仍然存在,可以安全访问}}

六、unique_ptr 与 shared_ptr 的核心差异

对比维度unique_ptrshared_ptr
所有权模型独占所有权共享所有权
拷贝语义禁止拷贝,仅支持移动支持拷贝
内存开销单指针大小指针 + 控制块
运行时开销几乎为零引用计数原子操作
线程安全仅对象本身引用计数线程安全
适用场景明确唯一所有者多对象共享资源

选择原则:
默认使用 unique_ptr;
确实需要共享时再使用 shared_ptr;
出现双向引用时引入 weak_ptr;
优先使用 make_unique / make_shared 以减少内存分配次数。

结语

智能指针是现代 C++ 内存管理的核心工具。RAII 提供了思想基础,unique_ptr 定义了清晰的独占所有权语义,shared_ptr 则通过引用计数实现了安全的共享模型,而 weak_ptr 负责解决循环引用这一经典难题。真正理解并掌握这些智能指针的实现原理,才能在工程实践中写出健壮、高性能、可维护的 C++ 代码。

码字不易,欢迎大家点赞,关注,评论,谢谢!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/1 4:50:16

飞书助力clawdbot成为本土化的AI助手

飞书助力clawdbot成为本土化的AI助手 先看视频 废话少说&#xff0c;先看视频&#xff1a; clawdbot 简介 视频是用飞书pc端模拟手机端&#xff0c;来控制clawdbot的。 clawdbot已经火到了macmini涨价的地步&#xff0c;为什么我们用起来那么难&#xff1f; 看看主页说明中…

作者头像 李华
网站建设 2026/5/1 7:02:23

一个相当复杂的跨品牌电梯智能群控系统项目,涉及硬件改造、软件调试和系统集成。从多奥提供的详细清单和流程来看,用户很可能是电梯智能化改造项目的技术负责人或系统集成商,需要确保整个方案从准备到验收的顺利

现在需要我帮助梳理和优化这个技术方案&#xff0c;使其更具可操作性和系统性。我打算从项目全生命周期的角度&#xff0c;构建一个逻辑清晰、阶段分明的实施框架。 首先考虑的是项目前期准备阶段&#xff0c;这是整个项目的基础。根据我看到的搜索结果&#xff0c;现场勘察与…

作者头像 李华
网站建设 2026/5/1 8:58:47

Dropbear SSH Server - 工程级 Bug 修复方案

一、Critical 级别修复(立即修复) BUG #1: circbuffer.c 空指针解引用 问题位置: circbuffer.c:93-106 原始代码: void cbuf_readptrs(const circbuffer *cbuf,unsigned char **p1, unsigned int *len1, unsigned char **p2, unsigned int *len2) {*p1 = &cbuf->…

作者头像 李华
网站建设 2026/5/1 10:30:42

2026美国大学生数学建模竞赛时间安排

2026美赛数学建模A题B题C题D题E题F题思路模型代码论文持续更新&#xff0c;完整论文见文末名片2026年MCM/ICM美赛已进入冲刺倒计时&#xff0c;各位参赛小伙伴想必都已组队完毕&#xff0c;摩拳擦掌准备迎战这96小时的脑力攻坚战&#xff0c;向着好成绩全力奔赴&#xff01;美赛…

作者头像 李华
网站建设 2026/5/1 8:57:32

2026美赛数学建模ABCDEF题题目选择注意事项

2026美赛数学建模ABCDEF题思路模型代码论文开赛后第一时间更新&#xff0c;完整内容见文末名片一、关于论文摘要和正文 务必逐一准确回答提出题目的问题。直触要害&#xff0c;不要绕弯&#xff0c;不要拖泥带水。说明问题的关键&#xff0c;本小组解决的方法、结果、创新性。论…

作者头像 李华
网站建设 2026/5/1 7:48:11

【开题答辩全过程】以 基于spring boot的国学诗词网站设计与实现--为例,包含答辩的问题和答案

个人简介一名14年经验的资深毕设内行人&#xff0c;语言擅长Java、php、微信小程序、Python、Golang、安卓Android等开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。感谢大家的…

作者头像 李华