【C++】智能指针模拟实现及详解

目录

什么是智能指针:

为什么要有智能指针:

auto_ptr:

unique_ptr:

shared_ptr:

shared_ptr的缺陷:

weak_ptr:


什么是智能指针:

概念:

        智能指针是一种特殊的类模板,用于自动管理具有动态分配生命周期的对象。它们通过模拟指针的行为来工作,但提供了自动的内存管理功能,从而减少了内存泄漏的风险。

        使用智能指针可以确保当它们所指向的对象超出作用域或被显式删除时,所指向的对象也会被自动删除。

通俗一点就是:智能指针就是一个类,使用RAII(Resource Acquisition Is Initialization)机制对普通指针进行一层封装,并重载*、-> 符号,让其可以像指针一样去使用,也就是让其用起来像个指针,本质是是一个对象,这样就可以方便的去管理一个对象的生命周期,减少内存泄漏的风险。

RAII:资源获取即初始化

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在 对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:1、不需要显示的释放资源,因为出了作用域会自动销毁。2、可以保证对象所需的资源在其生命周期内始终保持有效。

为什么要有智能指针:

在上面是什么智能指针的讲述中提到过一点:智能指针最重要的功能是提供自动的内存管理功能,减少内存泄露的风险。

这里先讲讲内存泄漏:

内存泄漏:

        内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄漏的危害:

        长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

内存泄漏分类:

我们一般关心以下两种方面的内存泄漏:

堆内存泄漏(Heap leak):

        堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一 块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

系统内存泄漏:

        指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放 掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

那么我们把眼光放在堆内存泄漏上,来看一下下面这段代码有没有什么问题:

int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}void Func()
{int* p1 = new int;int* p2 = new int;cout << div() << endl;delete p1;delete p2;
}int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}

这段程序中可以看出:如果发生除0错误抛异常的话会直接跳到try catch中,那么Func函数中创建出来的两个指针就不会被delete掉,从而引发内存泄漏。

那么如何解决呢? 这就可以使用智能指针去解决了,使用智能指针因为这是一个对象,在对象被创建时初始化好里面的指针,出了作用域会自动销毁去调用对象的析构函数,再在析构函数中释放掉指针即可:简易的智能指针模板代码形式如下:

template<class T>
class smart_ptr
{
public:// 创建时初始化smart_ptr(T* ptr) :_ptr(ptr){}~smart_ptr(){// 不需要显示的调用,因为对象除了作用域会自动调用析构函数if (_ptr)delete _ptr;}// 提供调用接口: 重载* ->T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr = nullptr;
};

理解完智能指针的概念就来看看以下的几种智能指针:

auto_ptr:

auto_ptr的实现原理:就是直接转移管理权,但是现在不推荐使用auto_ptr,先来看一下auto_ptr的简化版:

#include<memory>
template<class T>
class auto_Ptr
{
public:auto_Ptr(T* ptr):_ptr(ptr){}auto_Ptr(auto_Ptr<T>& sp){// 进行置空,交接sp的管理权_ptr = sp._ptr;sp._ptr = nullptr;}auto_Ptr<T>& operator=(auto_Ptr<T>& sp){// 检测是否自赋值:if (this != &sp){if (_ptr)delete _ptr;_ptr = sp._ptr;sp._ptr = NULL;}return *this;}~auto_Ptr(){if (_ptr){cout << "delete: " << _ptr << endl;delete _ptr;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};int main()
{auto_Ptr<int> p1(new int[10]);auto_Ptr<int> p2(p1);auto_Ptr<int> p3(new int[5]);auto_Ptr<int> p4 = p3;return 0;
}

通过上面的代码我们可以看出auto_ptr就只是简单的转移资源,为什么不推荐使用的原因也很简单,就是假如我把p1的资源转移了,但是我忘记了,或者我并不知道,然后又对p1进行操作,那么此时就会发生崩溃。所以很多公司都禁止使用auto_ptr,而是使用unique_ptr、shared_ptr。

unique_ptr:

C++11中开始提供更靠谱的unique_ptr,实现的原理就是:简单粗暴的防拷贝,下面是简化模拟实现的unique_ptr:

namespace yue
{template<class T>class unique_ptr{public:unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}// 获取_ptr的地址T* Get(){return _ptr;}unique_ptr(const unique_ptr<T>& sp) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;private:T* _ptr;};
}

unique_ptr直接将拷贝构造函数delete掉了,解决了因为拷贝带来的置空问题(“雀食” 是从根源上解决了问题)所以相对于auto_ptr来讲:

auto_ptr只是简单的转移管理权,被拷贝对象悬空,有风险,不建议使用。

unique_ptr不支持拷贝,没有风险,建议使用unique_ptr。

但是在某些情况下我确实是需要两个指针来管理同一块资源怎么办呢?我就是想让他能拷贝怎么办呢?诶!所以C++11就提供了更靠谱的并且支持拷贝的shared_ptr。

shared_ptr:

C++11提供了更靠谱的并且支持拷贝的shared_ptr,那么来思考一个问题,既然我这两个指针都是指向同一块资源,那么在两个指针都delete的时候,是不是就会引发崩溃呢?(因为同一块内存不能被释放两次)

既然shared_ptr支持拷贝,那么它就得确保即使多个指针指向同一块资源,在析构的时候也只会将内存释放一次:

可以看到程序是正常结束的,并没有出现崩溃,也就是并没有出现同一块资源被释放多次的行为,那是怎么做到的呢?

这里就要引入一个引用计数的概念了,如果有接触学过Linux的话应该很容易想起来,Linux建立硬链接的思路就是利用引用计数,如果不知道也没关系,请看如下:

也就是说在智能指针对象中再加一个参数,(假设为x),初始为1,当这个智能指针每被拷贝一次,这个x就++一下,被释放调用析构函数的时候这个值就--一下,如果这个值减到0了,就说明没有指针指向这块内存了,就可以直接delete掉这块内存了。

那么这个引用计数如何设计呢?

首先,可定不可以设计成(int x = 1;)这样子,因为多个智能指针管理同一个对象我们是只需要一个引用计数的,如果每个对象都有一个计数那肯定是不可以的,所以到这里,你是不是就立马想到设置成静态的、全局的(static),但是很遗憾,这样设置也不可以,为什么也不可以呢?

如果我们要使用static的话,那么大致模板是这样的:

template <class T>
class smart_Ptr
{
public:smart_Ptr(T* ptr) :_ptr(ptr){}smart_Ptr(const smart_Ptr<T>& sp):_ptr(sp._ptr){i++;}smart_Ptr<T>& operator=(const smart_Ptr<T>& ptr){// 不能自己给自己赋值if (_ptr != ptr._ptr){_ptr = ptr._ptr;++i;}return *this;}~smart_Ptr() {if (--i == 0){delete _ptr;cout << "~smart_Ptr()" << endl;}}int& Get(){return i;}private:T* _ptr = nullptr;   static int i;
};

但是看这个使用案例好像并没有什么错啊,p1p2指向一个,引用计数是2,p3指向一个,引用计数是1。

看起来没问题是正常的,因为这是我埋的坑,因为我使用了不同的类型去实例化模板,一个int,一个double,所以当然不会出错啦,static全局唯一就意味着如果是相同类型的话就算几个指针指向不同的对象,这个引用计数也还是会叠加,如下:

那既然使用不了static那么还有没有其他办法呢?当然有,我们可以直接new一个int*的指针,指向智能指针的引用计数,然后其他的就都一样,这样即使是相同类型,引用计数也是各自独立的。但是要注意,因为这个int*也是new出来的,所以在最后一个delete时要记得一并delete掉。

shared_ptr简化实现:

template<class T>
class shared_Ptr
{
public:shared_Ptr(T* ptr) :_ptr(ptr), _pcount(new atomic<int>(1)){}// sp2(sp1)shared_Ptr(const shared_Ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){(*_pcount)++;}// sp1 = sp2// 赋值还要考虑一块资源是否只有一个指针管控shared_Ptr<T>& operator=(const shared_Ptr<T>& sp){// 防止自赋值// 例如s1 = s1的情况,如果s1是管控资源的最后一个指针,按照逻辑上来是先析构的,会成随机值,引起错误if (_ptr != sp._ptr) {this->release(); // 查询管控数量(如果sp1是管理原本那一块资源的最后一个智能指针,得要先进行释放,才能进行sp1 = sp2的操作)_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);}return *this;}// 把析构要进行的操作分离成一个子函数,以便后面其他函数调用(因为函数调用内部一般不直接显式调用析构)void release(){// 最后一个管理的对象,释放资源if (--(*_pcount) == 0){cout << "delete: " << _ptr << endl;delete _ptr;delete _pcount;}}~shared_Ptr(){release();}// 查询引用计数int use_count(){return *_pcount;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;// 多线程环境下对同一shared_ptr执行写操作不是线程安全的,所以要设置成原子性的。atomic<int>* _pcount; // 引用计数
};

shared_ptr的缺陷:

得要注意一个循环引用的问题,来看下面这一段代码:

可以看到,并没有调用Node的析构函数(没有任何输出,正常应该输出~Node()),这就是因为循环引用造成了内存泄漏。

就算只是其中一个有指向都是可以正常释放的,如果都指向了就会出问题,就会触发循环引用,造成内存泄漏,其实就是当相互进行指向后,p1、p2的引用计数都为2,当主函数结束时,后定义的先析构,p2先析构,引用计数减到1,此时由p1的next管着p2,然后p1析构,引用计数也进行--,此时p1的引用计数也减到1,此时由p2的prev管理着p1,但是相互都不满足引用计数为0的条件,释放逻辑是一个死循环,无法释放这段内存,所以造成了内存泄漏。

那怎么解决呢?

这个地方是一个大坑,所以1为写代码的时候注意规范,尽量不要这么去进行操作相互指向。

2就是使用官方给出的weak_ptr,弱指针。

weak_ptr:

weak_ptr不支持直接管理智能指针,也就是不支持RAII,不单独管理资源,是用来辅助解决shared_ptr的循环引用问题。

如果你知道某个地方可能会构成循环引用,你就改成使用weak_ptr:

可以看到使用weak_ptr后就可以调用Node的析构函数,本质就是通过不增加引用计数来解决的,就算我知道p1的next指向p2,p2的prev指向p1,但是我不增加p1p2的引用计数,也就是:

赋值或拷贝时,只指向资源,但不增加shared_ptr的引用计数。

weak_ptr返回的引用计数就是shared_ptr的引用计数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/147645.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【计算机网络 - 基础问题】每日 3 题(十九)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏&…

java怎么给代码增加说明,让鼠标浮上去可以显示说明

java怎么给代码增加说明&#xff0c;让鼠标浮上去可以显示说明 简述 通过使用JavaDoc注释&#xff0c;你可以提高代码的可读性和可维护性&#xff0c;并且方便其他开发者理解你的代码。在Java中&#xff0c;为了让鼠标悬停在代码上时显示说明信息&#xff0c;你可以使用JavaDo…

​数据库: MyBatis-Plus

MyBatis-Plus MyBatis-Plus 是 MyBatis 的增强工具&#xff0c;核心作用是简化 CRUD 操作和提升开发效率。它提供基础的增删改查方法、分页插件、条件构造器以及代码生成器&#xff0c;帮助减少重复代码量。MyBatis-Plus 不支持自动建表&#xff0c;专注于简化数据库操作&…

CV之OCR:GOT-OCR2.0的简介、安装和使用方法、案例应用之详细攻略

CV之OCR&#xff1a;GOT-OCR2.0的简介、安装和使用方法、案例应用之详细攻略 目录 GOT-OCR2.0的简介 1、更新 GOT-OCR2.0的安装和使用方法 1、安装 安装环境cuda11.8torch2.0.1 安装包 安装Flash-Attention GOT权重&#xff1a;1.43G 2、演示 3、训练 4、评估 GOT-…

直接在tomcat下面访问jsp

复制一份tomcat为tomcat-8.5.99test 记住修改tomcat-8.5.99test下面bin/startup.sh&#xff08;Linux/Mac&#xff09;或 bin/startup.bat&#xff08;Windows&#xff09; 在 Linux/Mac export CATALINA_BASE/path/to/tomcat1 $CATALINA_HOME/bin/startup.sh 在 Windows: …

springboot 控制器

springboot 控制器 文章目录 springboot 控制器1.Controller和RestController**Controller&#xff1a;数据和页面****RestController&#xff1a;数据**所以我们的controller一般在springmvc中使用&#xff0c;返回页面&#xff0c;但是现在的项目基本上都是前后端分离项目&am…

手写SpringMVC

1、开发HspDispatcherServlet 2、完成客户端/浏览器可以请求控制层 目的&#xff1a;发出url请求时&#xff0c;经过前端控制器&#xff0c;找到Monster的List方法&#xff0c;把结果再打回去 3、从web.xml动态获取hspspringmvc.xml 4、完成自定义Service注解功能 目的&…

什么是 SaaS?(软件即服务)

什么是SaaS软件&#xff1f; 软件即服务&#xff08;SaaS&#xff09;是一种云计算方法&#xff0c;其中应用程序在线存储&#xff0c;并以订阅的方式提供给用户。SaaS解决方案可以从任何web浏览器中使用&#xff0c;而无需在pc端或服务器上安装软件。 SaaS消除了基础设施、升…

Microsoft Edge 五个好用的插件

&#x1f423;个人主页 可惜已不在 &#x1f424;这篇在这个专栏 插件_可惜已不在的博客-CSDN博客 &#x1f425;有用的话就留下一个三连吧&#x1f63c; 目录 Microsoft Edge 一.安装游览器 ​编辑 二.找到插件商店 1.打开游览器后&#xff0c;点击右上角的设置&#…

day-58 最佳观光组合

思路 用right表示最佳观光组合的右边的景点的索引&#xff0c;同时维护一个该景点左边values[left]left的最大值 解题过程 这样在遍历过程中&#xff0c;以right为右边景点的最佳得分即为values[right]-rightleftmax&#xff0c;再与ans比较&#xff0c;选择较大的那一个&…

ProtoBuf介绍及安装

文章目录 序列反序列化ProtoBuf特点安装ProtoBufwindowsUbuntuCentos 序列反序列化 在网络传输过程当中&#xff0c;可以理解为&#xff1a; 发送方接收方 它们彼此要通信&#xff0c;先要定好一个规则&#xff0c;也就是协议&#xff0c;双方都能认识的结构化数据&#xff…

优化 IT 投资与资源管理,实现企业数字化转型的成功

为何优化 IT 投资与资源管理对数字化转型至关重要 在当今瞬息万变的商业环境中&#xff0c;数字化转型已成为企业保持竞争力的必要手段。然而&#xff0c;随着技术的不断进步&#xff0c;企业在推进数字化进程时面临着巨大的IT 投资和资源管理挑战。优化这些投资不仅能确保企业…

算法.图论-并查集

文章目录 1. 并查集介绍2. 并查集的实现2.1 实现逻辑2.2 isSameSet方法2.3 union方法(小挂大优化)2.4 find方法(路径压缩优化) 3. 并查集模板4. 并查集习题4.1 情侣牵手4.2 相似字符串组 1. 并查集介绍 定义&#xff1a; 并查集是一种树型的数据结构&#xff0c;用于处理一些不…

突破距离限制:大文件跨境传输的高效策略揭秘

目前越来越多的人认识到大数据的重要性&#xff0c;有人将大数据比作“石油”&#xff0c;未来的某一天石油资源可能会面临枯竭&#xff0c;但是大数据中蕴藏的资源却不会。海外市场对于数据驱动的产品和服务的需求不断增加&#xff0c;越来越多的企业寻求更深度的跨国业务及合…

国内人工智能AI头部公司32家(包括详细技术、特点和综合实力)

国内AI头部公司前100家及其详细技术、特点和综合实力&#xff0c;基于当前行业认知和市场表现选取的一些代表性企业。 1. 百度&#xff08;Baidu&#xff09; 技术&#xff1a;百度在人工智能AI领域拥有深厚的技术积累&#xff0c;包括自然语言处理、计算机视觉、深度学习等核…

【文心智能体】 旅游手绘手帐 开发分享 零代码 手绘风景 记录行程和心情 旅游攻略

旅游手绘手帐&#xff0c;点击文心智能体平台AgentBuilder | 想象即现实 (baidu.com) 背景 这个智能体是一个零代码智能体&#xff08;文心智能体平台现主推零代码&#xff0c;低代码将于10月8日低代码下架迁移&#xff09;&#xff0c;同时它已公开配置详情&#xff0c;感兴趣…

使用centos7搭建wiki论坛,使用nginx网站来搭建wiki负载均衡,反向代理。

1.安装一个wget&#xff0c;进入目录opt下 #安装wget yum -y install wget#进入目录/opt/下面 cd /opt/2.获取 mysql8.0 rpm包,安装mysql8.0,安装mysql-server&#xff0c;yum会自动下载所需安装及依赖包. #获取 mysql8.0 rpm包 wget https://dev.mysql.com/get/mysql80-comm…

【文化课学习笔记】【物理】电场

【物理】电场 前置知识 绝缘体&#xff1a;本质是物体内部电荷无法自由移动。 导体&#xff1a;本质是物体内部电荷可以自由移动。 电荷的移动&#xff1a;导体内部能够发生自由移动的电荷只有负电荷。 显电性&#xff1a;显示的电性&#xff0c;是内部的正负电荷中和之后的结果…

NLP 文本分类任务核心梳理

解决思路 分解为多个独立二分类任务将多标签分类转化为多分类问题更换 loss 直接由模型进行多标签分类 数据稀疏问题 标注更多数据&#xff0c;核心解决方案&#xff1a; 自己构造训练样本 数据增强&#xff0c;如使用 chatGPT 来构造数据更换模型 减少数据需求增加规则弥补…

《2024中国AI大模型产业图谱2.0版》重磅发布

‍ 数据猿出品 本次“数据猿2024年度三大媒体策划活动——《2024中国AI大模型产业图谱3.0版》”正式发布。下一次版本迭代将于2024年12月底发布2024年3.0版&#xff0c;敬请期待&#xff0c;欢迎报名。 大数据产业创新服务媒体 ——聚焦数据 改变商业 随着科技的飞速发展&…