智能指针原理、使用和实现——C++11新特性(三)

目录

一、智能指针的理解

二、智能指针的类型

三、shared_ptr的原理

1.引用计数

2.循环引用问题

3.weak_ptr处理逻辑

四、shared_ptr的实现

五、定制删除器

六、源码


一、智能指针的理解

问题:什么是智能指针?为什么要有智能指针?智能指针解决什么样的问题?

要明白上面这些问题,我们先引入一个程序。

double div(double x, double y)
{int* p = new int(120);if (y == 0){string s = "y can not be zero.";throw s;}delete p;return x / y;
}
int main()
{try{double x = 2, y = 0;div(x, y);}catch (string s){cout << s << endl;}return 0;
}

上面这段程序,如果出现抛异常的话,p的内存是没有办法释放的。 

        智能指针简称RAll,是一种自动化管理资源的类模板,这里指的资源可以是:动态开辟的内存,文件指针,网络连接,互斥锁等等。RAII在获取资源时把资源委托给⼀个对象,接着控制对资源的访问,利用对象的⽣命周期结束时会自动析构的特性来完成对资源的自动释放,这样保障了资源的正常释放,避免资源泄漏问题。

二、智能指针的类型

       智能指针其实就是在原指针的基础上套一个类,如果就这样简单的想会有一个非常致命问题——当一块空间被两个智能指针对象指向时会析构两次,从而引发编译器报错

        库中的智能指针有很多,用法都一样,区别在于处理同一块空间会被释放两次的方法不同。接下来我们就来看这些智能指针是如何处理的。

  • auto_ptr:在做拷贝构造和赋值重载时,把原对象的资源管理权直接转移给新的对象,原对象指向空,保证一块资源只被一个智能指针管理,从而达到一块资源只被释放一次的目的。但是这种做法会使原指针悬空,如果被误用会导致访问报错的问题。推荐指数:⭐
  • unique_ptr:不允许做拷贝构造和赋值重载操作,从而达到一块资源只被释放一次的目的。如果用不到这两个函数的话可以考虑用这个智能指针。推荐指数:⭐⭐⭐⭐
  • shared_ptr:支持多个对象指向同一块资源,它底层用的是引用计数的方法,这是一个很优秀的智能指针,在下文我们会重点来学习该指针的具体原理和实现。推荐指数:⭐⭐⭐⭐⭐

三、shared_ptr的原理

1.引用计数

        原理:使用一个变量记录有几个对象在管理该资源,从而决定是否释放该资源。例如:当一个对象走到析构函数时也就是该对象生命结束了不再管理这块资源了,所以引用计数减一,此时如果引用计数变成0的话说明该资源没有对象管理了,可以直接释放,如果不是0,说明还有对象在管理,所以不用处理。

2.循环引用问题

        例如这样一个环形链表,当n1生命周期结束时发现,还有对象(n2的_next)在指向它,所以没有释放资源,当n2生命周期结束时发现还有对象(n1的_next)指向它,所以也没有释放资源。

        也可以这样想:n1的资源什么时候释放,因为n2还在用呢,需要n2的资源释放掉,那么n2的资源什么时候释放,因为n1还在用呢,需要n1的资源释放掉。循环往复始终释放不了,所以导致内存泄漏。

3.weak_ptr处理逻辑

        shared_ptr虽然很优秀,但依旧有缺陷——循环引用问题,所以就有了weak_ptr用来辅助shared_ptr来解决这个问题。

        weak_ptr不⽀持RAII,也不⽀持访问资源,所以我们看⽂档发现weak_ptr构造时不⽀持绑定到资源,只⽀持绑定shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引⽤计数,那么就可以解决上述的循环引⽤问题。

        weak_ptr没有重载operator*和operator->等,因为他不参与资源管理,那么如果他绑定的shared_ptr已经释放了资源,那么他去访问资源就是很危险的。weak_ptr⽀持expired检查指向的资源是否过期,use_count也可获取shared_ptr的引⽤计数,weak_ptr想访问资源时,可以调⽤lock返回⼀个管理资源的shared_ptr,如果资源已经被释放,返回的shared_ptr是⼀个空对象,如果资源没有释放,则通过返回的shared_ptr访问资源是安全的。

如下示例:

#include<iostream>
#include<memory>
using namespace std;
int main()
{shared_ptr<string> p(new string("left"));weak_ptr<string> pw(p);cout << pw.expired() << endl;cout << pw.use_count() << endl;auto p3 = pw.lock();cout << *p3 << endl;return 0;
}

四、shared_ptr的实现

计数器我们可以储存一个int*类型的指针来实现,然后需要一个指针来储存资源的地址。如下:

        构造函数:我们是要用_ptr储存资源的地址空间所以需要传入一个指针,同时需要给_count开辟空间,并且设为1。

My_share_ptr(T* ptr):_ptr(ptr), _count(new int(1)){}

        拷贝构造:只需要进行资源地址的拷贝和引用计数的拷贝,然后将_count++,表示该资源的管理者增加一个。

My_share_ptr(const My_share_ptr<T>& sptr)
{_ptr = sptr._ptr;_count = sptr._count;(*_count)++;
}//也可以用初始化列表的方式来写

拷贝赋值:这个比较复杂它考虑到的因素有很多。

  1. 自己给自己拷贝赋值:对于这种情况,直接做一个特判后直接退出函数,不用做任何操作。
  2. 对于=的左操作对象:在被赋值前先处理一下现在手上的资源,即让_count--然后判断_count是否为0,如果是则进行资源的释放,如果不是则不用做任何处理。
  3. 进行赋值。
  4. 对于=的右操作对象:该对象被拷贝了一份说明对应资源的管理者增加,需要_count++。
My_share_ptr<T>& operator=(const My_share_ptr<T>& sptr)
{if (sptr._ptr == _ptr) return *this;if (--(*_count) == 0){delete _ptr;delete _count;cout << "Delete1()" << endl;}_ptr = sptr._ptr;_count = sptr._count;(*_count)++;return *this;
}

对于其他运算符的重载比较简单,就不再多讲,我在下面的源码给出。

五、定制删除器

        在上面我们用的都是delete来释放资源,但是不够灵活,不同的资源用不同的释放方式,比如数组用delete[ ],文件资源需要用fclose来释放,否则就会出问题。所以就有了定制删除器,可以通过传入仿函数的方式指定资源的释放方式

        删除器如果通过模板参数来实例化的话会显得十分别扭。我们期望使用传参的方式传入删除器。但是又面临一个问题,我们并不知道这个仿函数是什么类型,该怎么储存呢?因为删除器是做资源释放,所以可以确定它的返回类型是void,参数是T*,那么此时此刻包装器就起到了巨大的作用。

        我们可以用function<void(T*)>类型接收并储存。同时把它做成一个全省参数,如下:

My_share_ptr(T* ptr, function<void(T*)> det= [](T* p) {delete p; }):_ptr(ptr),_count(new int(1)),_det(det){}

        当需要释放资源的时候不要用delete  _ptr,而是用_det(_ptr)即可。 _count是int*类型正常使用delete释放。

六、源码

#include<iostream>
#include<string>
#include<functional>
#include<memory>
using namespace std;
template<class T>
class My_share_ptr
{
public:My_share_ptr(T* ptr, function<void(T*)> det= [](T* p) {delete p; }):_ptr(ptr),_count(new int(1)),_det(det){}My_share_ptr(const My_share_ptr& sptr){_ptr = sptr._ptr;_count = sptr._count;_det = sptr._det;(*_count)++;}My_share_ptr& operator=(const My_share_ptr& sptr){if (sptr._ptr == _ptr) return *this;if (--(*_count) == 0){_det(_ptr);delete _count;cout << "Delete1()" << endl;}_ptr = sptr._ptr;_count = sptr._count;(*_count)++;return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}~My_share_ptr(){if (--(*_count) == 0){_det(_ptr);delete _count;cout << "Delete2()" << endl;}}
private:T* _ptr;int* _count;function<void(T*)> _det;
};
int main()
{//My_share_ptr<int> p(new int(8));//auto p2 = p;//My_share_ptr<int> c = p;My_share_ptr<int> p1(new int(8));My_share_ptr<int> p2(new int(7));My_share_ptr<int> p3=p1;p1 = p2;return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NIST 发布后量子密码学转型战略草案

美国国家标准与技术研究所 (NIST) 发布了其初步战略草案&#xff0c;即内部报告 (IR) 8547&#xff0c;标题为“向后量子密码标准过渡”。 该草案概述了 NIST 从当前易受量子计算攻击的加密算法迁移到抗量子替代算法的战略。该草案于 2024 年 11 月 12 日发布&#xff0c;开放…

使用uniapp开发微信小程序使用uni_modules导致主包文件过大,无法发布的解决方法

在使用uniapp开发微信小程序时候&#xff0c;过多的引入uni_modules的组件库&#xff0c;会导致主包文件过大&#xff0c;导致无法上传微信小程序&#xff0c;主包要求大小不超过1.5MB.分包大小每个不能超过2M。 解决方法&#xff1a;分包。 1.对每个除了主页面navbar的页面进…

WPF窗体基本知识-笔记-命名空间

窗体程序关闭方式 命名空间:可以理解命名空间的作用为引用下面的控件对象 给控件命名:一般都用x:Name,也可以用Name但是有的控件不支持 布局控件(容器)的类型 布局控件继承于Panel的控件,其中下面的border不是布局控件,panel是抽象类 在重叠的情况下,Zindex值越大的就在上面 Z…

【android USB 串口通信助手】stm32 源码demo 单片机与手机通信 Android studio 20241118

android 【OTG线】 接 下位机STM32【USB】 通过百度网盘分享的文件&#xff1a;USBToSerialPort.apk 链接&#xff1a;https://pan.baidu.com/s/122McdmBDUxEtYiEKFunFUg?pwd8888 提取码&#xff1a;8888 android 【OTG线】 接 【USB转TTL】 接 【串口(下位机 SMT32等)】 需…

SpringBoot源码解析(四):解析应用参数args

SpringBoot源码系列文章 SpringBoot源码解析(一)&#xff1a;SpringApplication构造方法 SpringBoot源码解析(二)&#xff1a;引导上下文DefaultBootstrapContext SpringBoot源码解析(三)&#xff1a;启动开始阶段 SpringBoot源码解析(四)&#xff1a;解析应用参数args 目录…

使用IDEA+Maven实现MapReduced的WordCount

使用IDEAMaven实现MapReduce 准备工作 在桌面创建文件wordfile1.txt I love Spark I love Hadoop在桌面创建文件wordfile2.txt Hadoop is good Spark is fast上传文件到Hadoop # 启动Hadoop cd /usr/local/hadoop ./sbin/start-dfs.sh # 删除HDFS的hadoop对应的input和out…

Spring Cloud Ribbon 实现“负载均衡”的详细配置说明

1. Ribbon 介绍 Ribbon 是什么 &#xff1f; 1.Spring Cloud Ribbon 是基于Netflix Ribbon 实现的一套客户端&#xff0c;负载均衡的工具 2.Ribbon 主要功能是提供客户端负载均衡算法和服务调用 3.Ribbon 客户端组件提供一系列完善的配置项如“连接超时&#xff0c;重试” 4…

TSMC12nm工艺数字IC后端实现难点都有哪些?

大家知道咱们社区近期TSMC 12nm ARM Cortexa-A72(1P9M 6Track Metal Stack)即将开班。这里小编要强调一点:不要认为跑了先进工艺的项目就会很有竞争力&#xff01;如果你仅仅是跑个先进工艺的flow&#xff0c;不懂先进工艺在数字IC后端实现上的不同点&#xff0c;为何有这样的不…

使用微信小程序调用飞桨PaddleX平台自行训练的模型——微信小程序用训练的牡丹花模型Demo测试

&#x1f3bc;个人主页&#xff1a;【Y小夜】 &#x1f60e;作者简介&#xff1a;一位双非学校的大二学生&#xff0c;编程爱好者&#xff0c; 专注于基础和实战分享&#xff0c;欢迎私信咨询&#xff01; &#x1f386;入门专栏&#xff1a;&#x1f387;【MySQL&#xff0…

【C++】基础篇二、引用详解

文章目录 1.引用的概念和定义2.引用的特性3.引用的使用3.1 做函数返回值3.2 传参 3.3 其他使用 4.const的运用引用的权限问题&#xff1b;const修饰引用&#xff1b; 5.指针和引用的对比 1.引用的概念和定义 在介绍概念之前先说一下引用的符号——“ & ”&#xff1b;对于这…

D3基础:绘制圆形、椭圆形、多边形、线、路径、矩形

在D3.js中&#xff0c;可以通过SVG元素来创建各种几何图形。以下是D3.js中常用的几何图形及其简单的创建方法&#xff1a; 1. 圆形 (Circle) 圆形是最基本的形状之一&#xff0c;可以通过<circle>标签来创建。 <!DOCTYPE html> <html> <head><met…

前端监控之sourcemap精准定位和还原错误源码

一、概述 在前端开发中&#xff0c;监控和错误追踪是确保应用稳定性和用户体验的重要环节。 随着前端应用的复杂性增加&#xff0c;JavaScript错误监控变得尤为重要。在生产环境中&#xff0c;为了优化加载速度和性能&#xff0c;前端代码通常会被压缩和混淆。这虽然提升了性…

MySQL的表的约束以及查询

本篇文章继续给大家梳理MySQL的操作 目录 表的约束 空属性 默认值 列描述 0填充 主键 主键常识 添加主键 删除主键 复合主键 自增长 唯一键 外键 单/多行输入与全/指定列的插入 全列输入 单行输入 多行插入 指定列插入 单行输入 多行插入 插入否则更新 替换…

MySQL 日志 主从复制

1. 日志 学习链接&#xff0c;click mysql中有4种日志&#xff1a; 错误日志二进制日志查询日志慢查询日志 1.1 错误日志 错误日志是MySQL中最重要的日志之一&#xff0c;它记录了当mysqld启动和停止时&#xff0c;以及服务器在运行过程中发生任何严重错误时的相关信息。当…

自动驾驶系列—深入解析自动驾驶车联网技术及其应用场景

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

基于YOLOv8深度学习的公共卫生防护口罩佩戴检测系统(PyQt5界面+数据集+训练代码)

在全球公共卫生事件频发的背景下&#xff0c;防护口罩佩戴检测成为保障公众健康和控制病毒传播的重要手段之一。特别是在人员密集的公共场所&#xff0c;例如医院、学校、公共交通工具等地&#xff0c;口罩的正确佩戴对降低病毒传播风险、保护易感人群、遏制疫情扩散有着至关重…

vue使用List.reduce实现统计

需要对集合的某些元素的值进行计算时&#xff0c;可以在计算属性中使用forEach方法 1.语法&#xff1a;集合.reduce ( ( 定义阶段性累加后的结果 , 定义遍历的每一项 ) > 定义每一项求和逻辑执行后的返回结果 , 定义起始值 ) 2、简单使用场景&#xff1a;例如下面…

【Linux】进程的优先级

进程的优先级 一.概念二.修改优先级的方法三.进程切换的大致原理&#xff1a;四.上下文数据的保存位置&#xff1a; 一.概念 cpu资源分配的先后顺序&#xff0c;就是指进程的优先权&#xff08;priority&#xff09;。 优先权高的进程有优先执行权利。配置进程优先权对多任务环…

【软件工程】一篇入门UML建模图(类图)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;软件开发必练内功_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前…

AWTK-WIDGET-WEB-VIEW 实现笔记 (3) - MacOS

MacOS 上实现 AWTK-WIDGET-WEB-VIEW 有点麻烦&#xff0c;主要原因是没有一个简单的办法将一个 WebView 嵌入到一个窗口中。所以&#xff0c;我们只能通过创建一个独立的窗口来实现。 1. 创建窗口 我对 Object-C 不熟悉&#xff0c;也不熟悉 Cocoa 框架&#xff0c;在 ChatGPT…