当前位置: 首页 > news >正文

【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++异常的处理方式🔍

相关文章:

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

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习C   &#x1f51d;&#x1f51d; C11 1. 前言2. 为什么要有智能指针?3. RAII思想…...

佳明(Garmin) fēnix 7X 增加小睡检测功能

文章目录 &#xff08;一&#xff09;零星小睡&#xff08;二&#xff09;小睡检测&#xff08;三&#xff09;吐槽佳明&#xff08;3.1&#xff09;心率检测&#xff08;3.2&#xff09;光线感应器&#xff08;3.3&#xff09;手表重量&#xff08;3.4&#xff09;手表续航 &a…...

二、如何保证架构的质量、架构前期准备、技术填补与崩溃预防、系统重构

1、如何保证架构的质量 -- 稳定性和健壮性 2、正确的选择是良好的开端 -- 架构前期准备 ① 架构师分类&#xff1a;系统架构师、应用架构师、业务架构师 3、技术填补与崩溃预防 4、系统重构...

14、SQL注入——HTTP文件头注入

文章目录 一、HTTP Header概述1.1 HTTP工作原理1.2 HTTP报文类型1.3 较重要的HTTP Header内容 二、HTTP Header注入2.1 HTTP Header注入的前提条件2.2 常见的HTTP Header注入类型 一、HTTP Header概述 1.1 HTTP工作原理 1.2 HTTP报文类型 &#xff08;1&#xff09;请求报文 …...

李宏毅bert记录

一、自监督学习&#xff08;Self-supervised Learning&#xff09; 在监督学习中&#xff0c;模型的输入为x&#xff0c;若期望输出是y&#xff0c;则在训练的时候需要给模型的期望输出y以判断其误差——有输入和输出标签才能训练监督学习的模型。 自监督学习在没有标注的训练…...

.Net6.0 Microsoft.AspNetCore.Http.Abstractions 2.20 已弃用

您想要升级 Microsoft.AspNetCore.Http.Abstractions 包&#xff0c;您需要注意以下几点&#xff1a; Microsoft.AspNetCore.Http.Abstractions 包在 ASP.NET Core 2.2 版本后已经被标记为过时&#xff0c;因为它已经被包含在 Microsoft.AspNetCore.App 框架引用中12。因此&am…...

c2-C语言--指针

1.用一级指针遍历一维数组 结论 buf[i]<>*(buf i) <> *(p i)<> p[i] #include <stdio.h>int main(){int buf[5] {10,20 ,30 ,40,50}; //buf[0] --- int // buf --&buf[0] ----int *int *p buf;//&buf[0] --- &*(buf0)printf(&quo…...

kafka入门(四):消费者

消费者 (Consumer ) 消费者 订阅 Kafka 中的主题 (Topic) &#xff0c;并 拉取消息。 消费者群组&#xff08; Consumer Group&#xff09; 每一个消费者都有一个对应的 消费者群组。 一个群组里的消费者订阅的是同一个主题&#xff0c;每个消费者接收主题的一部分分区的消息…...

DFS、BFS求解leetcode图像渲染问题(Java)

目录 leetcode733题.图像渲染 DFS BFS leetcode733题.图像渲染 733. 图像渲染 - 力扣&#xff08;LeetCode&#xff09; 有一幅以 m x n 的二维整数数组表示的图画 image &#xff0c;其中 image[i][j] 表示该图画的像素值大小。 你也被给予三个整数 sr , sc 和 newColor …...

0基础学习云计算难吗?

很多人经常会问云计算是什么&#xff1f;云计算能干什么&#xff1f;学习云计算能做什么工作&#xff1f;其实我们有很多人并不知道云计算是什么&#xff0c;小知今天来给大家讲讲学习云计算能做什么。 中国的云计算行业目前正处于快速发展阶段&#xff0c;随着互联网和数字化…...

【RabbitMQ高级功能详解以及常用插件实战】

文章目录 队列1 、Classic经典队列2、Quorum仲裁队列3、Stream流式队列4、如何使用不同类型的队列 二、死信队列 队列 classic经典队列&#xff0c;Quorum仲裁队列&#xff0c;Stream流式队列 1 、Classic经典队列 这是RabbitMQ最为经典的队列类型。在单机环境中&#xff0c…...

开源的数据流技术,该选择Redpanda还是Apache Kafka?

本文将比较Apache Kafka和Redpanda两种开源的数据流技术&#xff0c;在云原生实时处理能力上的不同&#xff0c;以及如何在项目中做出选择。 目前&#xff0c;Apache Kafka不但成为了数据流处理领域事实上的标准&#xff0c;而且带动了同类产品的出现。Redpanda就是其中之一…...

720度vr虚拟家居展厅提升客户的参观兴致

VR虚拟展厅线上3D交互展示的优势有以下几点&#xff1a; 打破了场馆的展示限制&#xff0c;可展示危险性制品、珍贵稀有物品、超大型设备等&#xff0c;同时提供了更大的展示空间和更丰富的展示内容。 可提供企业真实环境的实时VR全景参观&#xff0c;提升潜在客户信任度。 提供…...

mysql中的DQL查询

表格为&#xff1a; DQL 基础查询 语法&#xff1a;select 查询列表 from 表名&#xff1a;&#xff08;查询的结果是一个虚拟表格&#xff09; -- 查询指定的列 SELECT NAME,birthday,phone FROM student -- 查询所有的列 * 所有的列&#xff0c; 查询结果是虚拟的表格&am…...

【数据结构高阶】红黑树

目录 一、红黑树的概念 二、红黑树的性质 2.1 红黑树与AVL树的比较 三、红黑树的实现 3.1 红黑树节点的定义 3.2 数据的插入 3.2.1 红黑树的调整思路 3.2.1.1 cur为红&#xff0c;f为红&#xff0c;g为黑&#xff0c;u存在且为红 3.2.1.2 cur为红&#xff0c;f为红&am…...

Unity中Batching优化的GPU实例化(1)

文章目录 前言一、GPU实例化的规则1、网格一样&#xff0c;材质一样&#xff0c;但是材质属性不一样2、单个合批最大上限为511个对象3、只有OpenGL es 3.0及以上才支持&#xff08;3.0及以上有部分硬件可能也不支持&#xff09; 二、GPU实例化的应用场景1、公开几个成员属性&am…...

vue的data

类型&#xff1a;Object | Function 限制&#xff1a;组件的定义只接受 function。 详细&#xff1a; Vue 实例的数据对象。Vue 会递归地把 data 的 property 转换为 getter/setter&#xff0c;从而让 data 的 property 能够响应数据变化。对象必须是纯粹的对象 (含有零个或多个…...

Java基础课的中下基础课04

目录 二十三、集合相关 23.1 集合 &#xff08;1&#xff09;集合的分支 23.2 List有序可重复集合 &#xff08;1&#xff09;ArrayList类 &#xff08;2&#xff09;泛型 &#xff08;3&#xff09;ArrayList常用方法 &#xff08;4&#xff09;Vector类 &#xff08;…...

解决vue ssr服务端渲染运行时报错:net::ERR_PROXY_CONNECTION_FAILED

现象&#xff1a; 从代码里找了半天也没有找到问题&#xff0c;但是由于ssr服务端渲染配置本身非常复杂&#xff0c;步骤又繁琐&#xff0c; 而且报错又很多&#xff0c;不知道哪里出了问题。 感觉是header或者cookie丢失造成的&#xff0c;因为据说ssr本身有这样的缺陷&…...

APIFox:打造高效便捷的API管理工具

随着互联网技术的不断发展&#xff0c;API&#xff08;应用程序接口&#xff09;已经成为了企业间数据交互的重要方式。然而&#xff0c;API的管理和维护却成为了开发者们面临的一大挑战。为了解决这一问题&#xff0c;APIFox应运而生&#xff0c;它是一款专为API管理而生的工具…...

【解密LSTM、GRU如何解决传统RNN梯度消失问题】

解密LSTM与GRU&#xff1a;如何让RNN变得更聪明&#xff1f; 在深度学习的世界里&#xff0c;循环神经网络&#xff08;RNN&#xff09;以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而&#xff0c;传统RNN存在的一个严重问题——梯度消失&#…...

python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

12.找到字符串中所有字母异位词

&#x1f9e0; 题目解析 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义&#xff1a; 若两个字符串包含的字符种类和出现次数完全相同&#xff0c;顺序无所谓&#xff0c;则互为…...

tree 树组件大数据卡顿问题优化

问题背景 项目中有用到树组件用来做文件目录&#xff0c;但是由于这个树组件的节点越来越多&#xff0c;导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多&#xff0c;导致的浏览器卡顿&#xff0c;这里很明显就需要用到虚拟列表的技术&…...

【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅!

【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅! 🌱 前言:一棵树的浪漫,从数组开始说起 程序员的世界里,数组是最常见的基本结构之一,几乎每种语言、每种算法都少不了它。可你有没有想过,一组看似“线性排列”的有序数组,竟然可以**“长”成一棵平衡的二…...

Android屏幕刷新率与FPS(Frames Per Second) 120hz

Android屏幕刷新率与FPS(Frames Per Second) 120hz 屏幕刷新率是屏幕每秒钟刷新显示内容的次数&#xff0c;单位是赫兹&#xff08;Hz&#xff09;。 60Hz 屏幕&#xff1a;每秒刷新 60 次&#xff0c;每次刷新间隔约 16.67ms 90Hz 屏幕&#xff1a;每秒刷新 90 次&#xff0c;…...

【学习记录】使用 Kali Linux 与 Hashcat 进行 WiFi 安全分析:合法的安全测试指南

文章目录 &#x1f4cc; 前言&#x1f9f0; 一、前期准备✅ 安装 Kali Linux✅ 获取支持监听模式的无线网卡 &#x1f6e0; 二、使用 Kali Linux 进行 WiFi 安全测试步骤 1&#xff1a;插入无线网卡并确认识别步骤 2&#xff1a;开启监听模式步骤 3&#xff1a;扫描附近的 WiFi…...

【JavaEE】万字详解HTTP协议

HTTP是什么&#xff1f;-----互联网的“快递小哥” 想象我们正在网上购物&#xff1a;打开淘宝APP&#xff0c;搜索“蓝牙耳机”&#xff0c;点击商品图片&#xff0c;然后下单付款。这一系列操作背后&#xff0c;其实有一个看不见的“快递小哥”在帮我们传递信息&#xff0c;…...

codeforces C. Cool Partition

目录 题目简述&#xff1a; 思路&#xff1a; 总代码&#xff1a; https://codeforces.com/contest/2117/problem/C 题目简述&#xff1a; 给定一个整数数组&#xff0c;现要求你对数组进行分割&#xff0c;但需满足条件&#xff1a;前一个子数组中的值必须在后一个子数组中…...

运动控制--BLDC电机

一、电机的分类 按照供电电源 1.直流电机 1.1 有刷直流电机(BDC) 通过电刷与换向器实现电流方向切换&#xff0c;典型应用于电动工具、玩具等 1.2 无刷直流电机&#xff08;BLDC&#xff09; 电子换向替代机械电刷&#xff0c;具有高可靠性&#xff0c;常用于无人机、高端家电…...