【C++11】智能指针的使用以及模拟实现(shared_ptr,unique_ptr,auto_ptr,weak_ptr)

🌏博客主页: 主页
🔖系列专栏: C++
❤️感谢大家点赞👍收藏⭐评论✍️
😍期待与大家一起进步!


文章目录

  • 一、 RAII概念
  • 一、auto_ptr
    • 1.基本使用
    • 2.模拟实现
  • 二、unique_ptr
    • 1.基本使用
    • 2.模拟实现
  • 三、shared_ptr
    • 1.基本使用
    • 2.引用计数实现
    • 3.析构函数的升级(对于数组)
    • 4.循环引用(坑点)
    • 5.模拟实现
  • 四、weak_ptr


一、 RAII概念

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内
存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在
对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。
这种做法有两大好处:
1.不需要显式地释放资源。
2.采用这种方式,对象所需的资源在其生命期内始终保持有效

我们下面所说的智能指针都是基于这种思想设计出来的。

一、auto_ptr

1.基本使用

很多公司明确规定不准用auto_ptr,因为其坑点太多了。

因为其会导致管理权的转移,会导致被拷贝对象悬空,对悬空对象进行操作的时候就会发生报错

class A
{
public:A(int a = 0):_a(a){cout << "A(int a = 0)" << endl;}~A(){cout << this;cout << " ~A()" << endl;}int _a;
};
int main() {auto_ptr<A> ap1(new A(1));// 管理权转移,拷贝时,会把被拷贝对象的资源管理权转移给拷贝对象// 隐患:导致被拷贝对象悬空,访问就会出问题auto_ptr<A> ap2(ap1);// 崩溃ap1->_a++;ap2->_a++;return 0;
}

在这里插入图片描述

2.模拟实现

template<class T>class auto_ptr {public:auto_ptr(T *ptr):_ptr(ptr){}~auto_ptr() {delete _ptr;}// 像指针一样使用T& operator*() {return *_ptr;}T* operator->() {return  _ptr;}auto_ptr(auto_ptr<T>& ap):_ptr(ap._ptr){// 管理权转移//原指针发生悬空ap._ptr = nullptr;}auto_ptr<T>& operator=(auto_ptr<T>& ap){// 检测是否为自己给自己赋值if (this != &ap){// 释放当前对象中资源if (_ptr)delete _ptr;// 转移ap中资源到当前对象中_ptr = ap._ptr;ap._ptr = NULL;}return *this;}private:T* _ptr;};

二、unique_ptr

1.基本使用

unique_ptr这个智能指针对于auto_ptr的吐糟点解决的就非常简单粗暴,既然你拷贝与赋值都会造成那么多的问题,我直接把你的拷贝和赋值函数给禁用掉,当我们不需要拷贝的场景,建议使用这个智能指针
在这里插入图片描述
在这里插入图片描述

2.模拟实现

template<class T>class unique_ptr {public:unique_ptr(T*ptr):_ptr(ptr){}~unique_ptr() {delete _ptr;}T& operator*() {return *_ptr;}T* operator->() {return _ptr;}//直接把拷贝构造函数以及赋值运算符重载函数设置为删除函数unique_ptr(unique_ptr<T>& ap) = delete;unique_ptr<T>operator=(unique_ptr<T>& ap) = delete;private:T* _ptr;};

三、shared_ptr

1.基本使用

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共
    享。
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减
    一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对
    象就成野指针了。

在这里插入图片描述

2.引用计数实现

引用计数支持多个拷贝管理同一个资源,最后一个析构对象释放资源

private:T* _ptr;int* _pcount;//引用计数计数器function<void (T*)>_del = [](T* ptr) {delete ptr; };//析构函数包装器
~shared_ptr() {//引用计数的计时器对于指向同一个地址的指针肯定要是公有的//所以我们选择使用对指针解引用的方式改变计数器的值//而且改变了对两个指向同一个地址的指针都有影响if (--(*_pcount) == 0) {//(*_pcount) == 0说明此时已经没有指针指向这个位置,可以释放了cout << "delete:" << endl;_del(_ptr);delete _pcount;}}

3.析构函数的升级(对于数组)

当我们遇到数组的情况怎么办?我们析构的话只是析构当前这一个位置,而数组是一连串的,这个时候我们如果还用原来的老方法设计析构函数就会发生内存泄漏的问题

C++库里面选择自己去实现一个删除方法的类并且作为仿函数传进去
在这里插入图片描述

这个时候又衍生一个问题,我删除方法D作为模板参数是在析构函数内的,只在析构函数内有效,而不是在像模板参数T那样在整个类里面都有效,我们该如何解决这个问题呢?

答案:我们可以选择自己在private里面的再加入一个私有成员_del作为删除方法

这个时候又有人会有疑惑,我_del该如何既兼容释放一个地址,又兼容释放一串地址呢?我们在外面写的删除函数不一定能传进去适配_del的类型

答案:这个时候我们之前介绍的function包装器就能派上用场了【C++11】function包装器,bind函数模板使用
我们可以用一个包装器来兼容多个调用对象的类型,这样我们就能传入自己写的仿函数了。

template<class D>shared_ptr(T* ptr, D del): _ptr(ptr),_pcount(new int(1)),_del(del){}private:T* _ptr;int* _pcount;//引用计数计数器function<void (T*)>_del = [](T* ptr) {delete ptr; };//析构函数包装器//默认为释放一个地址的lambda的方法,我们要删除数组时可以自己写方法传进去,//因为释放地址的函数类型返回值都为void类型//当我们析构数组类型的时候可以设计的仿函数
template<class T>
struct DeleteArrayFunc {void operator()(T* ptr){ cout << "delete[]" << ptr << endl;delete[] ptr; }
};

4.循环引用(坑点)

在这里插入图片描述
在这里插入图片描述

  1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动
    delete。
  2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
  3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上
    一个节点。
  4. 也就是说_next析构了,node2就释放了。
  5. 也就是说_prev析构了,node1就释放了。
  6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev
    属于node2成员,所以这就叫循环引用,谁也不会释放。
    造成了类似死循环的场景

解决方案:
在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了

原理就是,node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和
_prev不会增加node1和node2的引用计数。既然我靠引用计数是否为0来判断是否需要析构,那么我不增加引用计数的个数不就可以了吗?

5.模拟实现

template<class T>class shared_ptr {public:shared_ptr(T*ptr=nullptr):_ptr(ptr),_pcount(new int(1)){}template<class D>shared_ptr(T* ptr, D del): _ptr(ptr),_pcount(new int(1)),_del(del){}~shared_ptr() {//引用计数的计时器对于指向同一个地址的指针肯定要是公有的//所以我们选择使用对指针解引用的方式改变计数器的值//而且改变了对两个指向同一个地址的指针都有影响if (--(*_pcount) == 0) {cout << "delete:" << endl;_del(_ptr);delete _pcount;}}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr),_pcount(sp._pcount){++(*_pcount);}shared_ptr<T>& operator=(const shared_ptr<T>& sp) {if (_ptr == sp._ptr) {return *this;}if (--(*_pcount) == 0){delete _ptr;delete _pcount;}_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);return *this;}int use_count()const  {return *_pcount;//获得引用计数的数}T* get()const {return _ptr;}T& operator*() {return *_ptr;}T* operator->() {return _ptr;}private:T* _ptr;int* _pcount;//引用计数计数器function<void (T*)>_del = [](T* ptr) {delete ptr; };//析构函数包装器};

四、weak_ptr

在这里先事先声明weak_ptr并不是RAII类型的指针,其设计的目的就是为了解决shared_ptr中的循环引用的问题。其不支持像其他智能指针的使用

template<class T>class weak_ptr {public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptr<T>& sp)  :_ptr (sp.get()){}weak_ptr<T>&operator=(const shared_ptr<T>& sp) {_ptr = sp.get();return *this;}/*shared_ptr<ListNode> node1(new ListNode);shared_ptr<ListNode> node2(new ListNode);//这不设计是为了让shared_ptr类型能够赋值给weak_ptr类型//我们知道node类型为shared_ptr类型,而其prev与next为weak_ptr类型node1->_next = node2;node2->_prev = node1;*/T& operator*() {return *_ptr;}T* operator->() {return _ptr;}private:T* _ptr;};

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

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

相关文章

网络层哪些事?

在本文讲解的网络层中&#xff0c;注意了解一下&#xff1a;IP协议&#xff01; 地址管理&#xff1a;每个网络上的设备&#xff0c;要能分配一个地址&#xff08;唯一&#xff09;路由选择&#xff1a;A给B发消息&#xff0c;具体走哪条路线&#xff1f;&#xff1f; IP地址&…

Linux/Ubuntu 安装 Java运行环境

linux下安装Java运行环境 1、下载安装包 .tar.gz 先在官网下载 JDK 点击这里 在这里要选择对应的 JDK 版本&#xff0c;一般我们目前选择JDK8 点击这里 2、在 /usr/local/ 目录下创建Java文件夹 cd /usr/local/ mkdir java3、将下载的文件通过FTP程序上传到刚刚创建的Java文…

服务器往浏览器推消息(SSE)应用

1&#xff0c;SSE 和 WebSocket 对比 SSE&#xff08;服务器发送事件&#xff09; SSE是一种基于HTTP的单向通信机制&#xff0c;用于服务器向客户端推送数据。它的工作原理如下&#xff1a; 建立连接&#xff1a;客户端通过发送HTTP请求与服务器建立连接。在请求中&#xff…

git学习——第4节 时光机穿梭

我们已经成功地添加并提交了一个readme.txt文件&#xff0c;现在&#xff0c;是时候继续工作了&#xff0c;于是&#xff0c;我们继续修改readme.txt文件&#xff0c;改成如下内容&#xff1a; Git is a distributed version control system. Git is free software. 现在&…

游戏中的随机——“动态平衡概率”算法

前言 众所周知计算机模拟的随机是伪随机&#xff0c;但在结果看来依然和现实中的随机差别不大。 例如掷硬币&#xff0c;连续掷很多很多次之后&#xff0c;总有连续七八十来次同一个面朝上的情况出现&#xff0c;计算机中一般的随机函数也能很好模拟这一点。 但在游戏中&…

Android Studio gradle手动下载配置

项目同步时&#xff0c;有时候会遇到Android Studio第一步下载gradle就是连接失败的问题。 这种情况&#xff0c;我们可以手动去gradle官网下载好gradle文件&#xff0c;放置在Android Studio的缓存目录下&#xff0c;这样AS在同步代码时就会自动解压下载好的文件。 步骤如下&…

6.SNMP报错-Error opening specified endpoint “udp6:[::1]:161“处理

启动SNMP服务 /etc/init.d/snmpd start 出现以下报错信息 [....] Starting snmpd (via systemctl): snmpd.serviceJob for snmpd.service failed because the control process exited with error code. See "systemctl status snmpd.service" and "journalctl…

有什么手机软件能分离人声和音乐?

很多人在制作混剪视频&#xff0c;需要二次创作的时候&#xff0c;就经常会把人声分离、背景音乐伴奏提取出来&#xff0c;然后重新加入自己的创意跟想法。下面就一起来看看如何用手机软件分离人声和音乐的吧&#xff01; 音分轨 一款可以分离人声和背景音乐的手机软件&#x…

eNSP笔记①

关闭范文信息&#xff1a;undo terminal monitor VRP三种试图 "<>"表示用户视图&#xff0c;系统默认的状态。主要用于查询设备基础信息或者状态等&#xff0c;也可以执行保存(save)。 “[]” 表示系统视图&#xff0c;在用户视图下输入system-view进入状态…

R语言进度条:txtProgressBar功能使用方法

R语言进度条使用攻略 在数据处理、建模或其他计算密集型任务中&#xff0c;我们常常会执行一些可能需要很长时间的操作。 在这些情况下&#xff0c;展示一个进度条可以帮助我们了解当前任务的进度&#xff0c;以及大约还需要多长时间来完成&#xff0c;R语言提供了几种简单且灵…

Excel提高工作效率常用功能

定位快捷键使用 CtrlG或者F5 根据不同类别插入空行 例&#xff1a;以下表&#xff0c;以部门为单位&#xff0c;每个部门后插入空白行 部门姓名出勤基本工资岗位津贴公体加班绩效基数工龄应发合计财务部姓名73115002101710财务部姓名11116006003401502363财务部姓名5271000…

maven 编译.../maven-metadata.xml 报错

文章目录 问题解决 问题 突然编译报错: 解决 打开maven的里离线工作模式,感觉就是下载包到本地. 一个是在maven设置里面 或者直接在maven编译的窗口:

游戏设计模式专栏(十二):在Cocos游戏开发中运用代理模式

点击上方亿元程序员关注和★星标 引言 大家好&#xff0c;我是亿元程序员&#xff0c;一位有着8年游戏行业经验的主程。 本系列是《和8年游戏主程一起学习设计模式》&#xff0c;让糟糕的代码在潜移默化中升华&#xff0c;欢迎大家关注分享收藏订阅。 代理模式&#xff08…

HTML 表格及练习

表格 概述 表格是一种二维结构&#xff0c;横行纵列。 由单元格组成。 表格是一种非常“强” 的结构&#xff1a; 每一行有相同的列数&#xff08;单元格&#xff09;&#xff0c;每一列有相同的行数&#xff08;单元格&#xff09; 同一列的单元格&#xff0c;宽度&#…

2023 年值得关注的软件测试趋势(3)

16.云性能工程对业务连续性的影响 检查和改进基于云的应用程序和服务的性能是云性能工程的主要目标&#xff0c;是各种软件测试趋势中云计算的重要组成部分。云提供了无与伦比的可扩展性、灵活性和成本节约&#xff0c;但如果没有适当的性能工程&#xff0c;组织将面临应用程序…

太好上手了!10款常用的可视化工具你一定要知道!

当谈到可视化工具时&#xff0c;有许多常用的工具可供选择。这些工具可以帮助我们将数据转化为易于理解和具有视觉吸引力的图表、图形和仪表板。 以下是10款常用的可视化工具&#xff0c;它们在不同领域和用途中广泛使用。 1. Datainside&#xff1a; Datainside是一款功能强…

Win+L不能锁屏

1、运行WINR&#xff0c;输入REGEDIT&#xff0c;运行注册表&#xff1a; 2、CTRLF&#xff0c;输入查找DisableLockWorkstation 3、双击这个&#xff0c;点十进制&#xff0c;输入0点确认即可。

Confluence 自定义博文列表

1. 概述 Confluence 自有博文列表无法实现列表自定义功能&#xff0c;实现该需求可采用页面中引用博文宏标签控制的方式 2. 实现方式 功能入口&#xff1a; Confluence →指定空间→创建页面 功能说明&#xff1a; &#xff08;1&#xff09;页面引用博文宏 &#xff08;…

基于安卓Android的掌上酒店预订APP

项目介绍 网络的广泛应用给生活带来了十分的便利。所以把掌上酒店预订与现在网络相结合&#xff0c;利用java技术建设掌上酒店预订APP&#xff0c;实现掌上酒店预订的信息化。则对于进一步提高掌上酒店预订发展&#xff0c;丰富掌上酒店预订经验能起到不少的促进作用。 掌上酒…

微信小程序获取手机号和openid

小程序通过wx.login组件会返回一个code&#xff0c;这个code用来获得用户的openid。 小程序写法为&#xff1a; wx.login({success (res) {if (res.code) {//发起网络请求wx.request({url: https://example.com/onLogin,// 后台给的请求地址data: {code: res.code}})} else {…