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

news2024/11/19 8:43:34

目录

一,为什么需要智能指针

二,内存泄露的基本认识

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.coloradmin.cn/o/1500844.html

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

相关文章

【重制版】WSDM 2024 2023时空时序论文总结

&#x1f31f;【紧跟前沿】“时空探索之旅”与你一起探索时空奥秘&#xff01;&#x1f680; 欢迎大家关注时空探索之旅 WSDM 2024于2024年3月4日-3月8日在墨西哥梅里达&#xff08;Mrida, Mxico&#xff09;正在举行。目前官网已经放出了所有被录用论文的表单&#xff08;链接…

2024037期传足14场胜负前瞻

2024037期售止时间为3月9日&#xff08;周六&#xff09;20点00分&#xff0c;敬请留意&#xff1a; 本期深盘多&#xff0c;1.5以下赔率4场&#xff0c;1.5-2.0赔率5场&#xff0c;其他场次是平半盘、平盘。本期14场整体难度中等。以下为基础盘前瞻&#xff0c;大家可根据自身…

干货 | MSC细胞培养 “秘籍”

MSC培养细节&#xff0c;这里有您想知道的~ MSC&#xff1a;间充质干细胞&#xff0c;是一群贴壁生长、形态类似于成纤维细胞的多能成体干细胞&#xff0c;存在于脐带、骨髓和脂肪组织等多种组织中&#xff0c;并且可以分化成多种不同的组 实验数据分享 1、样本&#xff1a;冻…

ChatGLM:CPU版本如何安装和部署使用

前段时间想自己部署一个ChatGLM来训练相关的物料当做chatgpt使用&#xff0c;但是奈何没有gpu机器&#xff0c;只能使用cpu服务器尝试使用看看效果 我部署的 Chinese-LangChain 这个项目&#xff0c;使用的是LLM&#xff08;ChatGLM&#xff09;embedding(GanymedeNil/text2vec…

Pytorch基础:Tensor的flatten方法

相关阅读 Pytorch基础https://blog.csdn.net/weixin_45791458/category_12457644.html?spm1001.2014.3001.5482 在Pytorch中&#xff0c;flatten是Tensor的一个重要方法&#xff0c;同时它也是一个torch模块中的一个函数&#xff0c;它们的语法如下所示。 Tensor.flatten…

达梦数据库——如何查看数据库大字段中的数据内容

今天get到一个小知识点 分享给大家&#xff0c;如何在数据库查看大字段中的数据内容。 以下为演示步骤&#xff0c;简单易懂&#xff0c;操练起来吧 首先创建一个含有CLOB、TEXT的大字段测试表 create table "SYSDBA"."CS"("COLUMN_1" CLOB,&qu…

JavaScript极速入门(2)

JQuery W3C标准给我们提供了一系列函数,让我们可以操作: 网页内容 网页结构 网页样式 但是原生的JavaScript提供的API操作DOM元素时,代码比较繁琐,冗长.我们学习使用JQuery来操作页面对象. JQuery是一个快速,简洁且功能丰富的JavaScript框架,于2006年发布.它封装JavaScript常…

干货!Python函数中的参数类型

1.必须参数 调用函数的时候&#xff0c;必须以正常的顺序传参&#xff0c;实参的数量和形参的数量保持一致 def demo(name, age):print("我的姓名是&#xff1a;%s, 年龄是&#xff1a;%d"%(name, age))demo("张三", 22) # 我的姓名是&#xff1a;张三…

黑马点评-发布探店笔记

探店笔记 探店笔记类似点评网站的评价&#xff0c;往往是图文结合。 对应的表有两个&#xff1a; tb_blog&#xff1a;探店笔记表&#xff0c;包含笔记中的标题、文字、图片等 tb_blog_comments&#xff1a;其他用户对探店笔记的评价 流程如下&#xff1a; 上传接口&#…

pytest测试框架使用基础07 fixture—parametrize获取参数的几种常用形式

【pytest】parametrize获取参数的几种常用形式: a.数据结构 b.文件 c.数据库 d.conftest.py配置一、直接在标签上传参 1.1 一个参数多个值 pytest.mark.parametrize("参数", (参数值1, 参数值2, 参数值3))示例&#xff1a; import pytest # 单个参数的情况 pytest.…

枚举 --java学习笔记

什么是枚举 枚举是一种特殊类 格式&#xff1a; 修饰符 enum 枚举类名{ 名称1&#xff0c;名称2&#xff0c;...&#xff1b; //枚举类的第一行必须罗列的是枚举对象的名字 其他成员... } 枚举类的第一行只能罗列一些名称&#xff0c;这些名称都是常量&#xff0c;…

[C++]类和对象,explicit,static,友元,构造函数——喵喵要吃C嘎嘎4

希望你开心&#xff0c;希望你健康&#xff0c;希望你幸福&#xff0c;希望你点赞&#xff01; 最后的最后&#xff0c;关注喵&#xff0c;关注喵&#xff0c;关注喵&#xff0c;大大会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的…

Python数据处理实战(4)-上万行log数据提取并作图进阶版

系列文章&#xff1a; 0、基本常用功能及其操作 1&#xff0c;20G文件&#xff0c;分类&#xff0c;放入不同文件&#xff0c;每个单独处理 2&#xff0c;数据的归类并处理 3&#xff0c;txt文件指定的数据处理并可视化作图 4&#xff0c;上万行log数据提取并作图进阶版&a…

Vue组件中的scoped属性

Vue组件中的scoped属性的作用是&#xff1a;当前的单文件组件的css样式只用于当前组件的template模板&#xff0c;在Vue脚手架汇总组件间关系时避免样式命名重复的情况。 原理&#xff1a;使用data-*属性在template模板中使用样式的HTML元素上添加额外属性&#xff0c;再利用标…

(sub)三次握手四次挥手

TCP的三次握手与四次挥手理解及面试题 序列号seq&#xff1a;占4个字节&#xff0c;用来标记数据段的顺序&#xff0c;TCP把连接中发送的所有数据字节都编上一个序号&#xff0c;第一个字节的编号由本地随机产生&#xff1b;给字节编上序号后&#xff0c;就给每一个报文段指派一…

即插即用篇 | 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

蓝桥杯2023年-买瓜(dfs,类型转换同样耗时)

题目描述 小蓝正在一个瓜摊上买瓜。瓜摊上共有 n 个瓜&#xff0c;每个瓜的重量为 Ai 。 小蓝刀功了得&#xff0c;他可以把任何瓜劈成完全等重的两份&#xff0c;不过每个瓜只能劈一刀。 小蓝希望买到的瓜的重量的和恰好为 m 。 请问小蓝至少要劈多少个瓜才能买到重量恰好…

Igraph入门指南 3

4、图转换到其他R数据结构 图是对实体关系的表达&#xff0c;在igraph中&#xff0c;图可以转换为三种数据结构。 4-1 图转邻接矩阵&#xff1a;as_adjacency_matrix | as_adj&#xff0c;结果是矩阵 邻接矩阵又分为有向图邻接矩阵和无向图邻接矩阵&#xff0c;但本函数使用…

MySQL-Linux安装

JDK安装&#xff08;linux版&#xff09; CentOS7环境&#xff1a; jdk下载地址huaweicloud.com 创建目录&#xff1a; mkdir /opt/jdk通过 ftp 客户端 上传 jdk压缩包&#xff08;linux版本&#xff09;到 1中目录进入目录&#xff1a;cd /opt/jdk解压&#xff1a;tar -zxv…

ArcGIS学习(十一)公服设施服务区划分与评价

ArcGIS学习(十一)公服设施服务区划分与评价 本任务带来的内容是公服设施服务区划分与公服设施服务区评价。本任务包括两个关卡: 公服设施服务区划分公服设施服务区空间价值评价1.公服设施服务区划分 首先,来看看这个案例的场景和基础数据。我们以上海市图书馆为例进行分析…