智能指针基础知识【C++】【RAII思想 || unique_ptr || shared_ptrweak_ptr || 循环引用问题】

目录

一,为什么需要智能指针

二,内存泄露的基本认识

1. 内存泄露分类

2. 常见的内存检测工具

3,如何避免内存泄露

三,智能指针的使用与原理

1. RAII思想

2. 智能指针

(1. unique_ptr

(2. shared_ptr & weak_ptr

 shared_ptr的循环引用问题

3. weak_ptr解决循环引用问题

4. 定制删除器(了解)


嗨!收到一张超美的风景图,愿你每天都能顺心! 

一,为什么需要智能指针

假设我们调用func函数,我们会发现:div函数操作,如果抛异常,则p1, p2就不会释放导致内存泄露。

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 ;
}

二,内存泄露的基本认识

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

1. 内存泄露分类

C/C++程序中一般我们关心两种方面的内存泄漏:
堆内存泄漏(Heap leak)(本章关注点)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

系统资源泄漏
指程序使用 系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

2. 常见的内存检测工具

linux下内存泄漏检测: Linux下几款C++程序中的内存泄露检查工具_c++内存泄露工具分析-CSDN博客

windows下使用第三方工具 : VS编程内存泄漏:VLD(Visual LeakDetector)内存泄露库_visual leak detector vs2020-CSDN博客

其他工具: 内存泄露检测工具比较 - 默默淡然 - 博客园 (cnblogs.com)

3,如何避免内存泄露

1. 采用 RAII思想或者智能指针来管理资源。
2. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带 内存泄漏检测的功能选项。 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。
总结一下:
内存泄漏非常常见,解决方案分为两种:1、事前预防型,如 RAII思想,智能指针等。2、事后查错型,如 泄漏检测工具

三,智能指针的使用与原理

1. RAII思想

RAII(Resource Acquisition Is Initialization)是一种在 利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。 在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效, 最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
  • 不需要显式地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效。
如下面例子:
// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:SmartPtr(T* ptr = nullptr): _ptr(ptr){}~SmartPtr(){if(_ptr)delete _ptr;}private:T* _ptr;
};
int div(){int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;}
void Func()
{ShardPtr<int> sp1(new int);ShardPtr<int> sp2(new int);cout << div() << endl;
}
int main()
{try {Func();}catch(const exception& e){cout<<e.what()<<endl;}return 0;
}

2. 智能指针

上述的SmartPtr之所以称为RAll思想,而不是智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此: AutoPtr模板类中还得需要将* 、->重载下,才可让其像指针一样去使用

所以,基本的框架就有了:

// 功能像指针一样
template <class T>
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){return &(*_ptr);}
private:T* _ptr;
};

这只是初步的框架,我们知道这个SmartPtr类的拷贝构造是浅拷贝。如果两个该类对象指向同一个内容,在析构时将会析构两次,进而出现报错。换个方向,这个类类似我们学习的迭代器,但我们当时没有考虑析构的问题?

答:迭代器只是管理数据的机构,数据析构是数据本身的事情。

说到拷贝,我们要认识智能指针的发展史:

C++98的不好用,在C++11中引入了unique_ptr, shared_ptr&weak_ptr,他们两来自Boost库 。

Boost的准标准库(标准库就是C++编译器就支持的,准标准库需要外部引入源码库的),怎么理解Boost? 可以理解为标准库的体验服。

(1. unique_ptr

主旨:简单粗暴禁止拷贝。(auto_ptr栽在拷贝上)

方法一:将构造函数声明并放入private区中,这样外部无法定义访问。

方法二:强制禁用

(2. shared_ptr & weak_ptr

玩法:引用计数,并且支持拷贝

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

 

下面是简单模拟实现的shared_ptr: 

template <class T>
class share_ptr
{
public:share_ptr(T* ptr):_ptr(ptr),_pcount(new int(1)),_mtx(new mutex){}~share_ptr(){destory();}void destory(){_mtx->lock();bool flag = false;if (--(*_pcount) == 0){cout << " delete :" << *_ptr << endl;delete _ptr;delete _pcount;flag = true;}_mtx->unlock();if (flag == true){delete _mtx;}}void AddCount(const share_ptr<T>& it){_mtx->lock();(*it._pcount)++;_mtx->unlock();}T& operator*(){return *_ptr;}T* operator->(){return &(*_ptr);}share_ptr(const share_ptr<T>& it):_ptr(it._ptr),_pcount(it._pcount),_mtx(it._mtx){AddCount(it);}share_ptr<T>& operator=(const share_ptr<T>& it){// 防止自己赋值自己导致数据丢失if (_ptr != it._ptr){destory();_ptr = it._ptr;_mtx = it._mtx;_pcount = it._pcount;AddCount(it);  // 计数++return *this;}}
private:T* _ptr;int* _pcount;mutex* _mtx;
};

 

 shared_ptr的循环引用问题

特征:互相使用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);
    cout << node1.use_count() << " "; // 打印链接数
    cout << node2.use_count() << endl;
    node1->_next = node2;
    node2->_prev = node1;
    cout << node1.use_count() << " ";
    cout << node2.use_count() << endl;
    return 0;}

 

3. weak_ptr解决循环引用问题

特征:

1. 不支持RAII思想

2. 像指针

3. 专门用来辅助shared_ptr,可以指向资源,但不管理资源,也不增加计数。

// 简化版本的weak_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 share_ptr<T>& sp){_ptr = sp.get();return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};

4. 定制删除器(了解)

我们注意到,我们的析构函数底层都是delete,但对象是数组,文件指针,delete析构就不合适,所以我们的shared_ptr有了定制删除器的用法。

 

本质上就是类似仿函数,下面是几个使用案例: 

template <class T>
struct DeleteArray
{void operator()(T* ptr){if (ptr != nullptr){delete[] ptr;}}
};
int main()
{// 自己写仿函数法shared_ptr<Date> pt1(new Date[10], DeleteArray<Date>());// 函数指针法这个过于简单的就不做演示// lambda法shared_ptr<Date> pt2(new Date[100], [](Date* ptr) { delete[] ptr; });// function对象法shared_ptr<Date> pt3(new Date[1000], function<void(Date*)> (DeleteArray<Date>()));// 文件管理shared_ptr<FILE> pt4(fopen("test.txt", "r"), [](FILE* ptr) { fclose(ptr); });cout << "endl";return 0;
}

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力。

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

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

相关文章

数据治理实战——翼支付金融板块业务数仓建设和数据治理之路

目录 一、数据治理背景 二、数据治理建设内容 2.1 组织协同 2.2 平台建设 2.3 数据应用治理 2.4 数据规范 2.5 数据安全 三、企业级数仓建设 3.1 调研阶段 2.2 平台护航 2.3 数仓分层 2.4 维度建模 2.4.1 维度建模四步曲 2.4.2 命名规范 2.4.3 资产沉淀 2.4.4 …

【黑马程序员】c++基础知识

文章目录 C初识编写hello world注释变量常量定义常量的两种方式 关键字标识符命名规则 数据类型整形sizeof关键字实型&#xff08;浮点型&#xff09;字符型转义字符字符串型布尔类型数据的输入 运算符程序流程结构顺序结构选择结构三种形式三目运算符switch语句 循环结构while…

百度智能云发布专用向量数据库 VDB 1.0,全新设计内核开启性能狂飙

1 专用向量数据库应对未来业务挑战 向量数据库 向量检索 数据库 向量数据库大致可以分为 2 部分&#xff1a;向量数据的检索&#xff0c;以及向量数据的存储和管理。 向量数据库的性能&#xff0c;比如高 QPS、低延时等&#xff0c;使得业务能够更快的响应用户的查询请求…

371. 牧师约翰最忙碌的一天(tarjan,2-SAT)

371. 牧师约翰最忙碌的一天 - AcWing题库 牧师约翰在 9 月 1 日这天非常的忙碌。 有 N 对情侣在这天准备结婚&#xff0c;每对情侣都预先计划好了婚礼举办的时间&#xff0c;其中第 i 对情侣的婚礼从时刻 Si 开始&#xff0c;到时刻 Ti 结束。 婚礼有一个必须的仪式&#xf…

2024 AI 辅助研发的新纪年

随着人工智能技术的持续发展与突破&#xff0c;2024年AI辅助研发正成为科技界和工业界瞩目的焦点。从医药研发到汽车设计&#xff0c;从软件开发到材料科学&#xff0c;AI正逐渐渗透到研发的各个环节&#xff0c;变革着传统的研发模式。在这一背景下&#xff0c;AI辅助研发不仅…

【Python】约瑟夫环问题

任务描述 据说著名历史学家 Josephus有过以下的故事&#xff1a;Josephus及他的朋友共41人围成一个圆圈&#xff0c;由第1个人开始报数&#xff0c;每数到3该人就必须出去&#xff0c;然后再由下一个人重新报数&#xff0c;直到圆圈上少于3人为止。Josephus 将朋友与自己安排在…

【kubernetes】关于k8s集群中的ingress规则案例

目录 一、k8s 对外服务之 Ingress 1.1什么是ingress 1.2外部的应用能够访问集群内的服务有哪些方案&#xff1f; 1.3Ingress 组成 1.4Ingress-Nginx 工作原理 1.5ingress 暴露服务的方式 二、实操ingress暴露服务 前期.部署 nginx-ingress-controller 2.1基于host网络…

RabbitMQ的Windows版安装教程

文章目录 前言一、Windows安装RabbitMQ总结 前言 曾经写过一篇关于RabbitMQ的Ubuntu安装教程&#xff08;http://t.csdnimg.cn/5CYfC&#xff09;&#xff0c;当时使用的是Docker将RabbitMQ安装到虚拟机上&#xff0c;但是有很多小伙伴问Windows上如何进行安装RabbitMQ&#x…

flink重温笔记(十二): flink 高级特性和新特性(1)——End-to-End Exactly-Once(端到端精确一致性语义)

Flink学习笔记 前言&#xff1a;今天是学习 flink 的第 12 天啦&#xff01;学习了 flink 高级特性和新特性之 End-to-End Exactly-Once&#xff08;端到端精确一致性语义&#xff09;&#xff0c;主要是解决大数据领域数据从数据源到数据落点的一致性&#xff0c;不会容易造成…

官宣!百度智能云千帆产品发布会3月21日北京见!

回望2023大模型狂奔的一年&#xff0c;百度智能云千帆大模型平台无疑是浓墨重彩的一笔。自2023年3月27日正式问世后&#xff0c;百度智能云千帆大模型平台以突飞猛进的速度持续发展。从模型、应用到生态&#xff0c;“千帆”书写着自身在大模型时代的答卷。 作为全球首个一站式…

web开发踩坑记

问题&#xff1a;npm install 在纯ipv6环境先无法安装包 现象&#xff1a; ping -6 registry.npmjs.org 是通的&#xff0c;说明可以解析到ipv6地址 解决方法&#xff1a; 在hosts文件中强制添加 registry.npmjs.org 解析到IPV6地址的记录 原因分析&#xff1a; 怀疑是npm的bug…

指针的学习5

目录 sizeof和strlen的区别 sizeof strlen 数组和指针笔试题解析 一维数组 字符数组 二维数组 指针运算笔试题解析 题目1&#xff1a; 题目2&#xff1a; 题目3&#xff1a; 题目4&#xff1a; 题目5&#xff1a; 题目6&#xff1a; 题目7&#xff1a; sizeof和…

Jmeter二次开发实现rsa加密

jmeter函数助手提供了大量的函数&#xff0c;像 counter、digest、random、split、strLen&#xff0c;这些函数在接口测试、性能测试中大量被使用&#xff0c;但是大家在实际工作&#xff0c;形形色色的测试需求不同&#xff0c;导致jmeter自带或者扩展插件给我们提供的函数无法…

MySQL GTID 简介 原理 应用场景 优点 注意事项

GTID&#xff08;Global Transaction Identifier&#xff09;是MySQL数据库中用于唯一标识事务的一种机制。GTID的引入旨在简化复制和故障恢复过程&#xff0c;确保数据一致性。在分布式系统和数据库复制中&#xff0c;GTID提供了一种跨多个数据库实例跟踪事务的方法。 GTID的…

vue3没有this怎么办?

在vue3中,新的组合式API中没有this,那我们如果需要用到this怎么办? 解决方法: getCurrentInstance 方法获取当前组件的实例&#xff0c;然后通过 ctx 或 proxy 属性获得当前上下文&#xff0c;这样我们就能在setup中使用router和vuex了 import { getCurrentInstance } from …

【无标题】linux 命令不生效,卡死

内核直接重启&#xff0c;不需要读取硬盘 [rootlocalhost opt]# ll-bash: /usr/bin/ls: Input/output error 解决方法 [rootlocalhost ~]# echo 1 > /proc/sys/kernel/sysrq [rootlocalhost opt]# echo b > /proc/sysrq-trigger

Redis中的SCAN渐进式扫描底层原理

Scan渐进式扫描原理 概述 由于Redis是单线程再处理用户的命令&#xff0c;而Keys命令会一次性遍历所有key&#xff0c;于是在命令执行过程中&#xff0c;无法执行其他命令。这就导致如果Redis中的key比较多&#xff0c;那么Keys命令执行时间就会比较长&#xff0c;从而阻塞Re…

代码随想录三刷 day17 | 二叉树之110.平衡二叉树 257. 二叉树的所有路径 404.左叶子之和

三刷day17 110.平衡二叉树257. 二叉树的所有路径404.左叶子之和 110.平衡二叉树 题目链接 解题思路&#xff1a; 求高度用后续遍历&#xff0c;先得到左子树和右子树的情况&#xff0c;然后再进行判断&#xff08;左右中&#xff09;使用递归遍历 代码如下&#xff1a; class…

即插即用篇 | YOLOv8 引入 ParNetAttention 注意力机制 | 《NON-DEEP NETWORKS》

论文名称:《NON-DEEP NETWORKS》 论文地址:https://arxiv.org/pdf/2110.07641.pdf 代码地址:https://github.com/imankgoyal/NonDeepNetworks 文章目录 1 原理2 源代码3 添加方式4 模型 yaml 文件template-backbone.yamltemplate-small.yamltemplate-large.yaml

程序员常用的几种算法

程序员常用的几种算法 一、程序员算法汇总二、程序员常用的几种算法1.选择排序算法1.1 选择排序算法解析&#xff1a;1.2 示例代码&#xff1a; 2.插入排序算法2.1 插入排序算法解析&#xff1a;2.2 示例代码&#xff1a; 3.冒泡排序算法3.1 冒泡排序算法解析&#xff1a;3.2 示…