【C++11(三)】智能指针详解--RAII思想循环引用问题

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:C++从入门到精通⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学习C++
  🔝🔝


在这里插入图片描述

C++11

  • 1. 前言
  • 2. 为什么要有智能指针?
  • 3. RAII思想以及智能指针的设计
  • 4. C++智能指针的发展历史
  • 5. shared_ptr模拟实现
  • 6. shared_ptr的循环引用问题
  • 7. 定制删除器
  • 8. 总结以及拓展

1. 前言

相信学C++的同学或多或少的听说过
智能指针这个词,博主刚听见这个词时
,觉得它应该很复杂,并且很高大上,但不
管是多牛的东西,都是人写出来的,是可
学习的!不要怀着害怕的心理来学习它

本章重点:

本篇文章着重讲解智能指针的发展历史
中出现过的auto_ptr,unique_ptr以及主
角shared_ptr.并且会介绍什么是RAII思想
以及为什么要有智能指针这一话题,最后
会给大家分析shared_ptr的循环引用问题
以及定制删除器的基本概念


2. 为什么要有智能指针?

在写代码时,我们经常在堆上申请空间
但是偶尔会忘记释放空间,会造成内存
泄漏问题,当然,这不是最重要的,在某些
场景下即使你释放了也会有问题:

int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void Func()
{// 1、如果p1这里new 抛异常会如何?// 2、如果p2这里new 抛异常会如何?// 3、如果div调用这里又会抛异常会如何?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;
}

在上面代码的这种场景中,不管是使用
new还是调用div函数都有抛异常的风险
并且程序一旦抛异常就会直接跳到catch
处,所以上面的代码一旦抛异常就代表着
delete p1和p2并不会执行,也就会出现
内存泄漏的问题!这个问题不使用智能
指针是很难解决的!!!


3. RAII思想以及智能指针的设计

  1. RAII思想

RAII思想是一种 利用对象生命周期来控制程序资源 (如内存、文件句柄、网络连接、互斥量等等)的简单技术。在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象

这种做法有两种好处:

  • 不需要显式地释放资源
  • 对象所需的资源在其生命期内始终有效
  1. 智能指针的基本设计

现在我们来写一个类,构造函数的
时候创造资源,析构函数的时候释放
资源,当对象出了作用域会自动调用析构!

// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr): _ptr(ptr)
{}
~SmartPtr()
{if(_ptr!=nullptr)delete _ptr;
}
T& operator*() {return *_ptr;}
T* operator->() {return _ptr;}
private:T* _ptr;
};

现在我们来使用一下它:

SmartPtr<int> sp1(new int(10));
*sp = 20;

当然,重载了->是给自定义类型用的


4. C++智能指针的发展历史

首先,我们要清楚智能指针的一个大坑
那就是当一个指针赋值给另外一个指针
时,我们需要的是浅拷贝,因为我们就是想
让两个指针指向同一块空间,但是指向了
同一块空间就会有析构函数调用两次的风险
由于这一个大坑,智能指针进行了很多次迭代

  1. 在C++98的时候就已经在库中实现
    了智能指针了,它就是 auto_ptr

在这里插入图片描述

既然智能指针是随着历史不断发展的
就证明它前面的版本写的不咋滴[doge]
事实也是如此,auto_ptr是这样实现的,
既然有析构两次的风险,那么当我把A
指针赋值给B指针后,A指针就销毁不能用
了,对于不了解auto_ptr的人来说这无疑是
一个巨大的风险!

auto_ptr<int> ap1(new int(10));
auto_ptr<int> ap2(ap1);
//此时ap1已经失效了!
  1. 有了这一大坑后,C++11推出了全新
    的智能指针: unique_ptr

在这里插入图片描述

unique_ptr的做法比auto_ptr还绝
智能指针不是拷贝有问题吗?那么
unique_ptr就禁用了拷贝和赋值,
很显然这也是一个坑,但是在实际
场景下,unique_ptr至少还能被用到
但auto_ptr是很多公司明令禁止使用的!

unique_ptr<int> up1(new int(10));
unique_ptr<int> up2(up1);//这里会直接报错
  1. 经过两次失败的智能指针后,C++11
    还推出了今天的主角: shared_ptr

在这里插入图片描述

shared_ptr可堪称完美的智能指针
也是实际中使用的最多的智能指针
它采用的是引用计数的思想,当指向
这份空间的计数是1时才析构,大于1
时就将计数减一,非常的优雅!

由于智能指针在面试时让手撕的概率很大
所以我们会模拟实现它


5. shared_ptr模拟实现

我们使用引用计数的方式来实现
shared_ptr,也就是在原先代码的
基础上增加一个int*成员变量来保存
还有几个指针指向当前空间!

template<class T>
class Smart_Ptr //实现的C++11的shared_ptr版本
{
public:Smart_Ptr(T* ptr = nullptr):_ptr(ptr),_pcount(new int(1)){}~Smart_Ptr(){Release();}Smart_Ptr(const Smart_Ptr<T>& sp):_ptr(sp._ptr),_pcount(sp._pcount){Addcount();}Smart_Ptr<T>& operator=(const Smart_Ptr<T>& sp){if (_ptr != sp._ptr){Release();_ptr = sp._ptr;_pcount = sp._pcount;Addcount();}return *this;}void Release(){if (--(*_pcount) == 0)//销毁最后一个变量时才释放资源{delete _ptr;delete _pcount;delete _pmtx;}}void Addcount(){(*_pcount)++;}void Subcount(){Release();private:T* _ptr;int* _pcount;
};

我们将计数++贺计数- -特意的提出来
这是因为很多场景下都需要这两个函数.
当计数不为1时就- -计数,当计数为一才
释放资源,并且这样写的好处是相同类型
的指针对象即使指向不同的空间也不会
出错,相反,使用static定义成员指针变量
就会出现上面的这种问题!


6. shared_ptr的循环引用问题

请看下面的代码运行会崩溃:

struct ListNode
{int _data;shared_ptr<ListNode> prev;shared_ptr<ListNode> next;~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{shared_ptr<ListNode> node1(new ListNode);shared_ptr<ListNode> node2(new ListNode);node1->next = node2;node2->prev = node1;return 0;
}

为啥会崩溃?下面我用画图加文字
的方式帮大家分析一下此问题:

在这里插入图片描述

现在来进一步分析:当main函数调用完,
node2会先析构,但是此时引用计数是2
所以不会释放空间而是将计数变为1.
然后node1再析构,同上,它的引用计数
也减为一,但是这两份空间并不会释放,
因为要node2的prev释放后,node1的空间
才会释放,那node2的prev什么时候释放?
答案是node2这份空间释放了才会释放
prev,那么node2这份空间什么时候释放?
答案是node1的next释放了它才释放,这
就形成了一个死循环,我等你释放了我才
能释放,对方也在等我释放了对方才能
释放,这就是"循环引用问题"

最好的解决方案就是在使用智能指针
的时候跳过这个坑,不用将智能指针和
这种场景一起使用!!!

在这里插入图片描述


7. 定制删除器

使用智能指针时可能会遇见下面的问题:

shared_ptr<int> sp1(new int[10]);

当变量出作用域销毁时即报错
因为new []对应的是delete [].
然而库中写法并不能识别有没有[]

还有一些问题:

shared_ptr<FILE> sp3(fopen("Test.cpp", "r"));

此时智能指针管理的对象并不是堆上
开辟的空间,delete完全没法用,此时需
要使用fclose,所以定制删除器非常重要

在这里插入图片描述

在构造函数的地方可以传入一个定制
删除器,也就是一个函数对象,此函数
中有对应的删除方法,请看下面的代码:

shared_ptr<int> sp2(new int[10], [](int* ptr) {delete[] ptr; });
shared_ptr<FILE> sp3(fopen("Test.cpp", "r"), [](FILE* ptr) {fclose(ptr); });

注:定制删除器属于了解的部分


8. 总结以及拓展

智能指针在面试中是常客,经常会被
问到发展历史和shared_ptr的手撕,
学到这里后,C++的所有重要的知识
差不多已经完结了,后面文章更新会慢一点

拓展:weak_ptr的拓展阅读

既然weak_ptr可以解决shared_ptr的
循环引用问题,那么什么是weak_ptr?
有兴趣的同学可以阅读下面这篇文章:

weak_ptr详解


🔎 下期预告:C++异常的处理方式🔍

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

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

相关文章

使用Python提取PDF文件中指定页面的内容

在日常工作和学习中&#xff0c;我们经常需要从PDF文件中提取特定页面的内容。在本篇文章中&#xff0c;我们将介绍如何使用Python编程语言和两个强大的库——pymupdf和wxPython&#xff0c;来实现这个任务。 1. 准备工作 首先&#xff0c;确保你已经安装了以下两个Python库&…

保姆级 | XSS Platform环境搭建

0x00 前言 XSS Platform 平台主要是用作验证跨站脚本攻击。该平台可以部署在本地或服务器环境中。我们可以使用 XSS Platfrom 平台搭建、学习或验证各种类型的 XSS 漏洞。 0x01 环境说明 HECS(云耀云服务器)xss platformUbuntu 22.04Nginx 1.24.0MySQL 5.6.51Pure-Ftpd 1.0.49…

LeetCode力扣每日一题(Java):35、搜索插入位置

一、题目 二、解题思路 1、我的思路&#xff08;又称&#xff1a;论API的重要性&#xff09; 读完题目之后&#xff0c;我心想这题目怎么看着这么眼熟&#xff1f;好像我之前学过的一个API呀&#xff01; 于是我回去翻了翻我之前写的博客&#xff1a;小白备战蓝桥杯&#xf…

Draw.io or diagrams.net 使用方法

0 Preface/Foreword 在工作中&#xff0c;经常需要用到框图&#xff0c;流程图&#xff0c;时序图&#xff0c;等等&#xff0c;draw.io可以完成以上工作。 official website:draw.io 1 Usage 1.1 VS code插件 draw.io可以扩展到VS code工具中。

Python自动化:selenium常用方法总结

使用的Python版本为3.8&#xff0c;selenium版本为4.15.2 Python自动化:selenium常用方法总结 1. 三种等待方式2. 浏览器操作3. 8种查找元素的方法4. 高级事件 1. 三种等待方式 强制等待 使用模块time下的sleep()实现等待效果隐式等待 使用driver.implicitly_wait()方法&#…

ChibiOS简介2/5

ChibiOS简介2/5 1. 源由2. ChibiOS基础知识2/52.4 Chapter 4 - ChibiOS General Architecture2.4.1 The Big Picture&#xff08;总体框图&#xff09;2.4.2 Embedded Components&#xff08;嵌入式组件&#xff09;2.4.3 Application Model&#xff08;应用模型&#xff09;2.…

Vue之数据绑定

在我们Vue当中有两种数据绑定的方法 1.单向绑定 2.双向绑定 让我为大家介绍一下吧&#xff01; 1、单向绑定(v-bind) 数据只能从data流向页面 举个例子&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"…

【Kubernetes】持久化存储emptyDir/hostPath/nfs/PVC

k8s持久化存储 一、为什么做持久化存储&#xff1f;二、k8s持久化存储&#xff1a;emptyDir三、k8s持久化存储&#xff1a;hostPath四、k8s持久化存储&#xff1a;nfs4.1、搭建nfs服务4.2、挂载nfs共享目录 五、k8s持久化存储&#xff1a; PVC5.1、什么是PV5.2、什么是PVC5.3、…

使用shell脚本将一台虚拟机上面数据分发到其他虚拟机上面

目录 1&#xff0c;功能2&#xff0c;注意点3&#xff0c;shell脚本介绍4&#xff0c;bash内容 1&#xff0c;功能 使用shell脚本将一台虚拟机上面数据分发到其他虚拟机上面。 2&#xff0c;注意点 需要修改的地方&#xff1a;hadoop250 hadoop251 hadoop252 hadoop253 hado…

普冉(PUYA)单片机开发笔记(4): 配置通用定时器

概述 在前几篇开发笔记中&#xff0c;从 PY32F003 的一个厂家标准例程开始&#xff0c;实现了中断式无阻塞串口收发、对开发板 LED3 的亮/灭控制&#xff0c;时钟系统的初始化和时钟选择。在此基础上&#xff0c;今天做一下定时器的应用实验。事先考虑以下几个问题&#xff1a…

使用Git进行版本控制

参考&#xff1a;《Python编程从入门到实践》 前言1、安装、配置 Git1.1 在Linux系统中安装Git1.2 在OS X系统中安装Git1.3 在Windows系统中安装Git1.4 配置Git 2、创建项目3、忽略文件4、初始化仓库5、检查状态6、将文件加入到仓库中7、执行提交8、查看提交历史 前言 版本控制…

回顾【数学基础】找出断层,继续前进, 使用chatGPT学习并解决实际问题:微积分

已经学过的算术、代数、几何。跳过。 从微积分开始 想象一下&#xff0c;你在画一条曲线&#xff0c;或者在一个大草地上奔跑。微积分就是一种数学工具&#xff0c;帮助我们了解这条曲线的形状&#xff0c;或者你奔跑的方式。 微分&#xff08;就像研究曲线上的每一小点&…