【C++进阶】RAII思想&智能指针
智能指针
- 一,为什么要用智能指针(内存泄漏问题)
- 内存泄漏
- 二,智能指针的原理
- 2.1 RAII思想
- 2.2 C++智能指针发展历史
- 三,更靠谱的shared_ptr
- 3.1 引用计数
- 3.2 循环引用
- 3.3 定制删除器
- 四,总结
上一节我们在讲抛异常时,就引出了利用智能指针来防止出现内存泄漏的问题,现在我们来看一下智能指针。
一,为什么要用智能指针(内存泄漏问题)
接着上一节的问题,如果我们在捕获异常时刚好在堆上new了一段空间,如果我们没有重新抛出异常,那么在堆上的空间该如何释放呢?
先来看一下这段代码:
int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void Func()
{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本身也会抛异常,如果p1这里抛异常或者p2这里抛异常,都会导致p1或者p2得不到释放。
如果在div中抛异常,那么p1和p2都不会得到释放。
所以在没有使用智能指针的情况下,这种问题还是很难解决的。
我们顺便回顾一下什么是内存泄漏。
内存泄漏
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费
如果长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终会卡死。这种造成的后果还是非常的严重的。
要避免出现内存泄漏,就要做到:
- 在前期做好良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。这个理想状态。如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。
- 采用
RAII思想或者智能指针来管理资源。
二,智能指针的原理
2.1 RAII思想
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。
这种做法有两大好处:
1. 不需要显式地释放资源,可以有效防止抛异常所导致的问题
2. 采用这种方式,对象所需的资源在其生命期内始终保持有效
智能指针就是这种思想的应用,这里我们先来简单实现一个智能指针,智能指针当然也是一种指针,所以也要重载->和*
也就是利用smart_ptr这个类的生命周期来控制资源
template<class T>
class smart_ptr {
public:smart_ptr(T* ptr):_ptr(ptr){}~smart_ptr() {delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void Func()
{smart_ptr <int> sp1(new int);smart_ptr <int> sp2(new int);cout << div() << endl;
}int main()
{try {Func();}catch(const exception& e){cout<<e.what()<<endl;}return 0;
}
当在main函数中捕获到异常后,会跳出Func这个函数的作用域,那么随之这两个smart_ptr 也会跟着销毁,同时在析构函数中将sp1和sp2跟着销毁。
但是这里又会有新的问题,如果要拷贝这个智能指针呢?
smart_ptr <int> sp1(new int);smart_ptr <int> sp2 = sp1;
这里就会造成这两个sp1和sp2管理的是同一块资源,出了这个作用域后会销毁,这时就会造成两次析构的问题。
那么要如何解决这个问题呢?我们就要从C++智能指针的发展来看看了。
2.2 C++智能指针发展历史
1.C++第一个智能指针是在C++98提出来的,就是auto_ptr。

但是auto_ptr有一个很大的问题就是,其实现的时候用的是管理权转移的思想,即:
auto_ptr<int> sp1(new int);
auto_ptr<int> sp2(sp1); // 管理权转移
sp1拷贝给sp2后,sp1就会失效,也就是说不论拷贝还是赋值后,原先的智能指针把对这块资源的管理权全部交给了被拷贝或者赋值的那个智能指针,然后会将原先的智能指针置为空。
这样就会很坑了,所以在很多使用智能指针的场景下,auto_ptr是很明令禁止不能使用的。
2.C++11又推出了新版本的智能指针,unique_ptr

那么unique_ptr是如何解决的呢?解决办法也比较简单粗暴,那就是不让拷贝或者赋值,也就是防拷贝
如何防拷贝呢?
- 只声明不实现
- 限定为私有
unique_ptr<int> sp1(new int);
unique_ptr<int> sp2(sp1);
这里可以看到是不可以进行拷贝的


3.如果就是要拷贝呢?
C++11之后又开始提供更靠谱的并且支持拷贝的shared_ptr
shared_ptr是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。也就是当指向某个资源的个数为1时才释放。
我们具体在下面来进行讲解并且模拟实现一下:
三,更靠谱的shared_ptr
3.1 引用计数
shared_ptr是用引用计数的思想来保证可以去拷贝和赋值的。也就是shared_ptr在其内部,给每个指向的资源都维护了着一份计数,用来记录该份资源被几个对象共享


如果某个shared_ptr的引用计数是0,就说明自己是最后一个使用该资源的对象,就必须释放该资源;如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
赋值也是一样的,赋值后也要将对用的引用计数进行改变

下面是模拟实现的代码:
template<class T>
class shared_ptr {
public:shared_ptr(T* ptr = nullptr):_ptr(ptr),_pcount(new int(1)){}void release(){if (--(*_pcount) == 0){//cout << "delete->" << _ptr << endl;//delete _ptr;delete _pcount;}}~shared_ptr() {release();}//拷贝构造shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){(*_pcount)++;}//赋值shared_ptr operator=(const shared_ptr<T>& sp) {//这里要分情况(如果被拷贝的对象引用计数只有一次就要释放)if (_ptr != sp._ptr) {//这里直接判断其指向的是不是同一个资源,可以解决直接或者间接自己给自己赋值的情况release();_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);}return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}int use_count() const{return *_pcount;}T* get() const{return _ptr;}
private:T* _ptr;int* _pcount;
};
注意:这里在重载=时要处理一下自己给自己赋值的情况
3.2 循环引用
C++11出现的shared_ptr虽然解决了拷贝的问题,但是又引出了新的问题,就是
循环引用的问题
我们来结合下面的场景来分析一下循环引用:
看下面的代码:
struct ListNode
{int val;shared_ptr<ListNode> next;shared_ptr<ListNode> prev;~ListNode(){cout << "~ListNode()" << endl;}
};int main(){shared_ptr<ListNode> n1(new ListNode);shared_ptr<ListNode> n2(new ListNode);n1->next = n2;n2->prev = n1;return 0;
}
运行后我们看到n1和n2并没有被释放。但是如果我们只要屏蔽掉n1->next=n2或者n2->prev = n1,n1和n2都会释放。
没有释放那就是内存泄漏!!
这是为什么呢?

那么这样的问题要如何解决呢?
这里就要用到weak_ptr了,weak_ptr是专门用来解决循环引用的问题的,他不是RAII思想,不会增加引用计数。
这里的解决办法就是将ListNode里的next和prev用weak_ptr代替
weak_ptr<ListNode> next;
weak_ptr<ListNode> prev;
这里可以看到weak_ptr是支持用shared_ptr去赋值的

这里我们简单实现了weak_ptr,想看的大家可以进入我的gitee查看:智能指针
3.3 定制删除器
这里还有一个小问题就是,如果我们使用智能指针所控制的这个空间不是new出来的而是new [],或者malloc出来的,那么我们在使用时就会出问题,因为shared_ptr在内部是delete。
shared_ptr<ListNode> sp1(new ListNode[10]);
这里就要用到定制删除器,其实就是传入一个仿函数(lambda表达式也可以)去删除指定类型。
template<class T>
struct DeleArry {void operator()(T* ptr) {delete[] ptr;}
};shared_ptr<ListNode> sp1(new ListNode[10],DeleArry<ListNode>());
或者lambda表达式
shared_ptr<ListNode> sp3(new ListNode[10], [](ListNode* ptr) { delete[] ptr; });
当然相应的shared_ptr的结构也要做相应的变化,感兴趣大家可以自己简单模拟实现一下。
大家也可以来看看我模拟实现的:智能指针
四,总结
这里我们也就讲完了智能指针了,智能指针还是很实用的。到这里我们C++的大部分重难点也就结束了。如果你到了这里,首先恭喜你坚持学习到了现在并且感受到了C++的独特之处。但是我们的学习还远没有结束,下一节我们会讲解类的设计模式中的常用的模式之一:单例模式。希望大家可以持续关注。
相关文章:
【C++进阶】RAII思想&智能指针
智能指针 一,为什么要用智能指针(内存泄漏问题)内存泄漏 二,智能指针的原理2.1 RAII思想2.2 C智能指针发展历史 三,更靠谱的shared_ptr3.1 引用计数3.2 循环引用3.3 定制删除器 四,总结 上一节我们在讲抛异…...
探索量子计算:打开未来技术的大门
在科技领域,每一次技术革命都能开启新的可能性,推动人类社会进入一个新的时代。当前,量子计算作为一种前沿技术,正引领着下一轮科技革命的浪潮。本文将深入探索量子计算的奥秘,解析其工作原理,并通过一个简…...
C++11 设计模式2. 简单工厂模式
简单工厂(Simple Factory)模式 我们从实际例子出发,来看在什么情况下,应用简单工厂模式。 还是以一个游戏举例 //策划:亡灵类怪物,元素类怪物,机械类怪物:都有生命值࿰…...
RabbitMQ-死信队列常见用法
目录 一、什么是死信 二、什么是死信队列 编辑 三、第一种情景:消息被拒绝时 四、第二种场景:. 消费者发生异常,超过重试次数 。 其实spring框架调用的就是 basicNack 五、第三种场景: 消息的Expiration 过期时长或队列TTL…...
2024/4/14周报
文章目录 摘要Abstract文献阅读题目创新点CROSSFORMER架构跨尺度嵌入层(CEL)CROSSFORMER BLOCK长短距离注意(LSDA)动态位置偏置(DPB) 实验 深度学习CrossFormer背景维度分段嵌入(DSW)…...
MySQL 社区版 安装总结
很早就安装过MySQL,没有遇到过什么问题,直接next就行了,这次在新电脑上安装却遇到了一些问题,记录一下。 安装的是MySQL社区版,下载地址是www.mysql.com,进入后选择DOWNLOAD页面,选择MySQL Com…...
二叉排序树的增删改查(java版)
文章目录 1. 基本节点2. 二叉排序树2.1 增加节点2.2 查找(就是遍历)就一起写了吧2.3 广度优先遍历2.4 删除(这个有点意思)2.5 测试样例 最后的删除,目前我测试的是正确的 1. 基本节点 TreeNode: class TreeNode{pri…...
linux下coredump问题的定位分析方法
(Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu) 参考:https://blog.csdn.net/m0_73698480/article/details/130077852 最近定位了一段时间linux下的崩溃问题,又收集了一些思路,特整理记录一下。 常见coredump定位方法是:…...
第十届蓝桥杯省赛真题(C/C++大学B组)
目录 试题 A: 组队 试题 B: 年号字串 试题 C: 数列求值 试题 D: 数的分解 试题 E: 迷宫 试题 F: 特别数的和 试题 G:完全二叉树的权值 试题 H:等差数列 试题 I:后缀表达式(不一定对) 试题 J:灵能…...
Scrapy 爬取m3u8视频
Scrapy 爬取m3u8视频 【一】效果展示 爬取ts文件样式 合成的MP4文件 【二】分析m3u8文件路径 视频地址:[在线播放我独自升级 第03集 - 高清资源](https://www.physkan.com/ph/175552-8-3.html) 【1】找到m3u8文件 这里任务目标很明确 就是找m3u8文件 打开浏览器…...
LVGL简单记录
1、 vs中代码旁边有个小锁删除git 2、Visual Studio 试图编译已删除的文件, 如果这个文件也是你不再需要编译的文件,且已经从文件系统中删除,你需要从 .vcxproj 文件中移除或者注释掉这一行,以停止Visual Studio尝试去编译一个不…...
计算机网络——ARP协议
前言 本博客是博主用于复习计算机网络的博客,如果疏忽出现错误,还望各位指正。 这篇博客是在B站掌芝士zzs这个UP主的视频的总结,讲的非常好。 可以先去看一篇视频,再来参考这篇笔记(或者说直接偷走)。 …...
【C++]C/C++的内存管理
这篇博客将会带着大家解决以下几个问题 1. C/C内存分布 2. C语言中动态内存管理方式 3. C中动态内存管理 4. operator new与operator delete函数 5. new和delete的实现原理 6. 定位new表达式(placement-new) 1. C/C内存分布 我们先来看下面的一段代码和相关问题 int global…...
深入理解计算机网络分层结构
一、 为什么要分层? 计算机网络分层的主要目的是将复杂的网络通信过程分解为多个相互独立的层次,每个层次负责特定的功能。这样做有以下几个好处: 模块化设计:每个层次都有清晰定义的功能和接口,使得网络系统更易于设…...
亚马逊云科技CTO带你学习云计算降本增效秘诀
2023亚马逊云科技一年一度的重磅春晚--Re:invent上有诸多不同话题的主题Keynote,这次小李哥带大家复盘来自亚马逊CTO: Wener博士的主题演讲: 云架构节俭之道1️⃣节俭对于云计算为什么重要? ▶️企业基础设施投入大,利用好降本策略可以减少巨…...
快速上手Vue
目录 概念 创建实例 插值表达式 Vue响应式特性 概念 Vue是一个用于 构建用户界面 的 渐进式 框架 构建用户界面:基于数据渲染出用户看到的页面 渐进式:Vue相关生态:声明式渲染<组件系统<客户端路由<大规模状态管理<构建工具 V…...
java 目录整理
Java知识相关目录主要参考黑马程序员 风清扬老师的视屏,参考链接为 Java_黑马刘意(风清扬)2019最新版_Java入门视频_Java入门_Java编程_Java入门教程_黑马教程_黑马程序员_idea版_哔哩哔哩_bilibili 1、java 基础 java基本认识?java跨平台原理?jdk、jre、jvm的联系? 链接:…...
使用Python的Pillow库进行图像处理书法参赛作品
介绍: 在计算机视觉和图像处理领域,Python是一种强大而流行的编程语言。它提供了许多优秀的库和工具,使得图像处理任务变得轻松和高效。本文将介绍如何使用Python的wxPython和Pillow库来选择JPEG图像文件,并对选中的图像进行调整和…...
docker 容器指定utf-8编码
在运行 Docker 容器的时候,如果容器内应用需要使用 UTF-8 编码来正常处理中文,你可以通过设置环境变量来指定编码。 可以使用 -e 或者 --env 标志来设置环境变量。比如,设置 LANG 和 LC_ALL 环境变量为 C.UTF-8 或者 en_US.UTF-8:…...
单例模式以及常见的两种实现模式
单例模式是校招中最常考的设计模式之一. 设计模式其实就是类似于“规章制度”,按照这个套路来进行操作。 单例模式能保证某个类在程序中只存在唯一 一份实例。而不会创建出多个实例,如果创建出了多个实例,就会编译报错。而不会创建出多个实…...
从原理图到Vivado:手把手教你搞定XC7Z020-CLG400的EMIO引脚分配与约束
从原理图到Vivado:手把手教你搞定XC7Z020-CLG400的EMIO引脚分配与约束 在ZYNQ7000系列开发中,EMIO引脚的正确分配与约束是实现PS与PL协同工作的关键环节。许多工程师在初次接触ZYNQ架构时,往往会被MIO、EMIO和AXI_GPIO的关系所困扰ÿ…...
流处理优化:提高实时数据处理性能
流处理优化:提高实时数据处理性能 一、流处理优化概述 1.1 流处理优化的定义 流处理优化是指通过优化流处理系统的性能、吞吐量和延迟,提高实时数据处理能力的过程。它涉及优化数据处理管道、资源配置和算法实现。 1.2 流处理优化的价值 低延迟ÿ…...
TextInputLayout实战:从属性解析到自定义样式进阶
1. TextInputLayout基础入门:从零开始掌握Material输入框 第一次接触TextInputLayout时,我被它丝滑的浮动提示动画惊艳到了。相比传统的EditText,这个Material Design组件确实能让表单界面瞬间提升好几个档次。记得去年做登录页面重构时&…...
Windows10系统V-rep安装避坑指南:从百度网盘资源到环境配置
1. 为什么选择V-rep以及准备工作 如果你是机器人学或仿真技术的初学者,V-rep(现更名为CoppeliaSim)绝对是一个值得尝试的仿真平台。它轻量级、跨平台,而且对硬件要求不高,特别适合在个人电脑上进行算法验证和教学演示…...
为AI智能体构建可编程邮箱:mailbot实战指南
1. 项目概述:为AI智能体打造专属的“可编程邮箱”如果你正在开发一个AI智能体,无论是客服机器人、自动化工作流还是个人助理,让它具备收发邮件的能力往往是刚需。传统的做法是什么?要么去折腾Gmail的API,忍受OAuth授权…...
Excel数据同步ERP/CRM太麻烦?一个Python脚本搞定多系统自动填充(基于GoBot)
Excel数据同步ERP/CRM太麻烦?一个Python脚本搞定多系统自动填充(基于GoBot) 每次月底看着财务同事在ERP系统里逐条录入Excel数据,市场部同事又在CRM里重复同样的操作,这种低效场景你一定不陌生。数据在不同系统间的孤岛…...
基于React与Tailwind CSS的轻量级ChatGPT Web界面部署与定制指南
1. 项目概述与核心价值最近在折腾AI应用开发,发现很多朋友都想自己部署一个轻量级的ChatGPT对话服务,但面对动辄几个G的模型和复杂的部署流程就望而却步。直到我发现了blrchen/chatgpt-lite这个项目,它完美地解决了这个问题——一个真正轻量、…...
在Nodejs后端服务中集成Taotoken实现稳定可靠的大模型调用
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 在Nodejs后端服务中集成Taotoken实现稳定可靠的大模型调用 将大模型能力集成到后端服务是现代应用开发的常见需求。对于Node.js开发…...
构建个人技能库:从代码片段到可复用技能单元的设计与实践
1. 项目概述:当代码遇上魔法,技能库的构建哲学在软件开发的日常里,我们常常会羡慕那些“魔法师”般的同事:他们似乎总能信手拈来一段代码,优雅地解决一个棘手问题;或者拥有一个私人的“百宝箱”,…...
Flair NLP框架:从入门到精通的7步完整学习指南 [特殊字符]
Flair NLP框架:从入门到精通的7步完整学习指南 🚀 【免费下载链接】flair A very simple framework for state-of-the-art Natural Language Processing (NLP) 项目地址: https://gitcode.com/gh_mirrors/fl/flair Flair是一个简单而强大的自然语…...
