【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.mzph.cn/news/213446.shtml

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

相关文章

30.如何在Spring所有Bean创建完后做扩展?

如何在Spring所有Bean创建完后做扩展? 哪里才算所有的bean创建完了。 首先是所有的配置bean会注册成BeanDefinition 然后根据BeanDefinition进行循环调用一个一个的getBean进行生产。 循环完所有的BeanDefiniton,通过BeanFactory的getBean方法生成所有的Bean 这个循环结…

LightDB - 支持substring_index 函数[mysql兼容]

从 23.4 版本开始&#xff0c; LightDB 支持 mysql 的substring_index 函数。下面对这个函数进行介绍 substring_index(str, delim, count ) 这个函数用于从指定字符串str中返回到达分隔符delim出现次数(count)之前的子字符串。。具体见之后用例&#xff1a; mysql 中介绍&a…

【BUG】微信小程序image不会随着url动态变化

问题描述&#xff1a; 第一次打开界面&#xff0c;显示的是默认头像而不是用户头像&#xff0c;似乎image里面的src只要第一次有值就不会再更新了 解决 不要给src里面的变量设置初始值&#xff0c;而是直接赋空值

信息安全、网络安全和数据安全的相互关系

最近正在开展安全方面的相关工作&#xff0c;因此就对这些概念做了一些分析&#xff0c;参考各种介绍和书籍&#xff0c;结合自身的认识&#xff0c;总结起来如下&#xff0c;信息安全、网络安全、数据安全和基础设施安全的关系究竟是什么&#xff0c;信息安全概念最大&#xf…

DevOps搭建(七)-安装Jenkins详细步骤

这里我们用Docker进行安装 1、拉取Jenkins镜像 Jenkins download and deployment 选择LTS长期支持的版本,接着点击Docker链接进入 找到上面的版本,并copy拉取镜像的命令 docker pull jenkins/jenkins:2.426.1-lts 2、docker-compose安装Jenkins 首先创建安装目录/home/f…

STM32 cubeMX 呼吸灯实验

文章代码使用 HAL 库。 文章目录 一、1.PWM原理二、LED 原理图三、使用cubemx 配置 led四、PWM 相关函数五、PWM占空比占空比计算六、PWM 呼吸灯重要代码总结 呼吸灯 一、1.PWM原理 PWM全称为脉冲宽度调制&#xff08;Pulse Width Modulation&#xff09;&#xff0c;是一种常…

拥有大量虾皮买家号有哪些好处

拥有众多Shopee买家账号&#xff0c;无疑是卖家们获取极大优势的一项策略。多账号的运用不仅有助于卖家在Shopee平台上获得更为丰富的流量&#xff0c;更能够在关键词排名和销售表现等方面为其带来显著提升。 首先&#xff0c;多个Shopee买家账号的灵活运用&#xff0c;使卖家能…

前后端(JAVA)实现AES对称加解密方式

文章目录 前后端&#xff08;JAVA&#xff09;实现AES对称加解密方式1 对称加密分类以及概括1.1 加密安全等级 DES < 3DES < AES < RC1.2 DES1.3 3DES1.4 AES1.5 RC 2 前后端实现AES对称加解密方式3 后端AES对称加解密&#xff08;ECB和CBC模式&#xff09;工具类4 前…

【Python百宝箱】从传感器到云端:深度解析Python在物联网中的多面应用

迈向智能未来&#xff1a;Python与物联网生态系统的完美融合 前言 随着物联网技术的不断发展&#xff0c;Python作为一种灵活且强大的编程语言&#xff0c;逐渐成为物联网开发的重要工具之一。本文将深入探讨物联网领域中常用的Python库和框架&#xff0c;涵盖了从轻量级通信…

JavaScript <有道翻译之数据解密‘23年12月06日版‘>--案例(三)

前言: 记得上半年还是去年,有道翻译还是直接返回明文数据;现在也跟着,用接口返回加密数据了; 娱乐一下,破他的密文数据... 成品效果图: js部分: 对于找他的密文数据有点费时,针对密文--->搜他地址和启动器不是特别容易,辗转多时(搜:descrypt/json.parse 结合使用更快),有图…

通讯录实现

下方是头文件的代码 #define _CRT_SECURE_NO_WARNINGS #include <assert.h> #include<stdio.h> #include<string.h> #include<stdlib.h>#define NAME_MAX 20 #define SEX_MAX 6 #define TELE_MAX 12 #define ADDR_MAX 30 #define MAX 100 #define D…

swing快速入门(四)

注释很详细&#xff0c;直接上代码 上一篇 增加内容 流式布局范例 import java.awt.*;public class swing_test_2{public static void main(String[] args){//创建一个窗口对象Frame framenew Frame("test");//设置窗口大小frame.setSize(800,800);//这里演示的是…

Gateway全局异常处理及请求响应监控

前言 我们在上一篇文章基于压测进行Feign调优完成的服务间调用的性能调优&#xff0c;此时我们也关注到一个问题&#xff0c;如果我们统一从网关调用服务&#xff0c;但是网关因为某些原因报错或者没有找到服务怎么办呢&#xff1f; 如下所示&#xff0c;笔者通过网关调用acc…

中小企业管理者如何培育团队精神?

某石油工程有限公司总经理曾提问&#xff1a;“作为中小企业的管理者如何才能更好的激发团队精神呢&#xff1f;” 每个企业都向往和号召团队精神&#xff0c;但是往往事与愿违。在各种羡慕嫉妒恨的情绪影响下&#xff0c;难免会产生一些落差&#xff0c;影响到团队精神。 所…

超声波清洗机会损伤物品吗?一文明白超声波清洗机有哪些优点

正确使用超声波清洗机且买对超声波清洗机是不会对清洗物品造成伤害的&#xff01; 一、超声波清洗机工作原理是如何的&#xff1f; 超声波清洗机的工作原理是利用超声波产生的空化振动来清洁物体。当超声波在清洗液中传播时&#xff0c;它会产生微小的气泡和振动&#xff0c;这…

论jenkins的使用方法(初步)

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 目录 &#x1f4d1;什么是持续集成&…

1-1、Java概述

语雀原文链接 文章目录 1、Java发展2、Java体系结构3、Java特点 1、Java发展 1990年&#xff0c;Sun公司(Stanford University Network,斯坦福大学网络公司)詹姆斯高斯林推出的一门语言最开始注册的名字oak语言(橡树)&#xff0c;重名了被迫改成Java2009年Sun公司被甲骨文Ora…

Docker 容器中使用 Docker - DinD 和 DooD

突然间研究这个来的缘由是正在从 Jenkins 往 Harness 的过度, 而完全用命令来构建 Docker 镜像变得不一样了。在 Jenkins 中 Agent 本身也是一个 Docker Daemon, 所以 Docker 命令执行无障碍&#xff0c;而 Harness 的所谓的 Agent 就是一个个的运行在 Kubernetes 中的 Docker …

error:gmapping

– Could not find the required component ‘gmapping’. The following CMake error indicates that you either need to install the package with the same name or change your environment so that it can be found. CMake Error at /opt/ros/kinetic/share/catkin/cmake…

logstash插件简单介绍

logstash插件 输入插件(input) Input&#xff1a;输入插件。 Input plugins | Logstash Reference [8.11] | Elastic 所有输入插件都支持的配置选项 SettingInput typeRequiredDefaultDescriptionadd_fieldhashNo{}添加一个字段到一个事件codeccodecNoplain用于输入数据的…