C++知识点 -- 智能指针

news2024/11/18 9:35:16

C++知识点 – 智能指针

文章目录

  • C++知识点 -- 智能指针
  • 一、智能指针的使用及原理
    • 1.使用场景
    • 2.RAII
    • 3.智能指针的设计思想
    • 4.智能指针的拷贝问题
  • 二、auto_ptr
  • 三、unique_ptr
  • 四、shared_ptr
    • 1.模拟实现
    • 2.shared_ptr的循环引用
  • 五、weak_ptr
  • 六、定制删除器
  • 七、内存泄漏
    • 1.什么是内存泄漏
    • 2.内存泄露的危害
    • 3.如何避免内存泄漏


一、智能指针的使用及原理

1.使用场景

在这里插入图片描述
对于上面的场景,p1和p2在new申请空间后,div函数如果出现了除0错误,那么程序就会抛出异常,跳到接受异常的程序段继续执行,p1和p2申请的空间就没有被正常释放,造成了内存泄漏;
这种场景我们就可以使用智能指针来解决空间的释放问题。

2.RAII

RAII(Resource Acquisition Is Initialization)获取到资源立即初始化,是一种利用对象生命周期来控制程序资源的技术;
在对象构造时获取资源,接着控制对资源的访问使其在对象的生命周期内都有效,最后在对象析构时释放资源,我们实际上把管理一份资源的责任托管给了一个对象,好处在于:
(1)不需要显式地释放资源;
(2)采用这种方式,对象所需的资源在其生命周期内始终有效;

3.智能指针的设计思想

(1)利用RAII的思想设计delete资源的类;
(2)像指针一样的行为;

因此,智能指针实际上是一个对象,这个对象重载了operator->和operator*,具有像指针一样的行为;

template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		: _ptr(ptr)
	{}

	~SmartPtr()
	{
		cout << "delete:" << _ptr << endl;
		delete _ptr;
	}

	T* operator->()
	{
		return _ptr;
	}

	T& operator*()
	{
		return *_ptr;
	}

private:
	T* _ptr;
};

上面的代码就是一个简易的智能指针,能够实现资源的释放,以及像指针一样的行为;

double Div(int a, int b)
{
	if (b == 0)
	{
		throw invalid_argument("除0错误");
	}
	return (double)a / b;
}

void test1()
{
	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2(new int);

	cout << Div(2, 0) << endl;

}

int main()
{
	try
	{
		test1();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

使用智能指针对上面的代码进行重写,让智能指针托管开辟的新资源,当sp1和sp2析构时,其析构函数会delete其管理的资源;
在这里插入图片描述

4.智能指针的拷贝问题

编译器自动生成的拷贝构造函数对于内置类型会完成浅拷贝,这里的拷贝需要的就是浅拷贝,因为深拷贝会将管理的资源在其他地方拷贝一份,违背了功能需求;
但是浅拷贝又带来了delete多次造成程序崩溃的问题,因此c++库中设计了几种智能指针来解决拷贝问题;

二、auto_ptr

这是c++98版本中提供的智能指针,auto_ptr对于拷贝的处理是管理权转移
下面是它的模拟实现:

	template <class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr)
			: _ptr(ptr)
		{}

		auto_ptr(auto_ptr<T>& ap)
			: _ptr(ap._ptr)
		{
			//管理权转移
			ap._ptr = nullptr;
		}

		auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			//检测是否为自己给自己赋值
			if (this != &ap)
			{
				//释放当前对象中的资源
				if (_ptr)
				{
					delete _ptr;
				}

				//转移ap中的资源到当前对象
				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}
			return *this;
		}

		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}

		T* operator->()
		{
			return _ptr;
		}

		T& operator*()
		{
			return *_ptr;
		}

	private:
		T* _ptr;
	};

auto_ptr对于拷贝的处理方式是管理权转移,对于拷贝构造,auto_ptr将资源的管理权转移给新的对象,原来的对象的指针置空;
对于赋值重载,auto_ptr清空等号左边的对象的资源,然后将等号右边的对象管理的资源转交给左边的对象,右边对象的指针置空;

auto_ptr对于智能指针拷贝的处理不是很好,所以很多项目都会禁止使用auto_ptr;

三、unique_ptr

c++11中开始提供unique_ptr,对拷贝的处理方式就是禁止拷贝
下面是它的模拟实现:

	template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			: _ptr(ptr)
		{}

		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}

		T* operator->()
		{
			return _ptr;
		}

		T& operator*()
		{
			return *_ptr;
		}

		unique_ptr(const unique_ptr<T>& up) = delete;//只声明不实现
		unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;

	private:
		T* _ptr;
	};

unique_ptr将拷贝构造和赋值重载都进行了只声明不实现的操作,这样类中就不会生成默认的拷贝构造和赋值重载了,也就禁止了unique_ptr对象间的拷贝;

四、shared_ptr

shared_ptr支持拷贝,原理是通过引用计数的方式来实现多个shared_ptr对象之间共享资源:
(1)每个资源都维护着一份计数,用来记录该份资源被几个对象共享;
(2)在对象被销毁时,对象引用计数减一;
(3)如果引用计数是0,就说明自己是最后一个被销毁的对象,就必须释放该资源;
(4)如果引用计数不是0,就不能释放该资源;

1.模拟实现

如何实现多个对象共享一个引用计数呢?不能将引用计数设为static成员变量,因为这样所有的资源对象都是用一个引用计数;可以将引用计数的成员便变量设置成一个指针,只在对象构造的时候new一个引用计数,在拷贝和赋值的时候,只增加引用计数;

	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			: _ptr(ptr)
			, _pCount(new int(1)) // 只有在对象调用构造函数的时候,才会新建一个管理资源的引用计数
		{}

		shared_ptr(const shared_ptr<T>& sp)
			: _ptr(sp._ptr)
			, _pCount(sp._pCount) // 拷贝构造的时候不新建引用计数,只拷贝资源指针和计数指针,并且计数++
		{
			*(_pCount)++;
		}

		T* operator->()
		{
			return _ptr;
		}

		T& operator*()
		{
			return *_ptr;
		}
		
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			--(*_pCount); //指针指向新的资源,原来指向的资源计数需要--

			_ptr = sp._ptr;
			_pCount = sp._pCount;

			(*_pCount)++;

			return *this;
		}

	private:
		T* _ptr;

		//引用计数
		int* _pCount;
	};

上面代码中的赋值重载是有问题的:
在这里插入图片描述
在这种情况下,sp1,2,3全部都赋值sp5,原先sp1,2,3指向的资源的引用计数就减为了0,但是上面的赋值重载函数并不能将原先的资源释放掉,就造成了内存泄露;
同时,还需要解决自己给自己赋值的问题(这里还要注意指向同一份资源之间的对象相互赋值);

		void Release()
		{
			if (--(*_pCount) == 0)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
				delete _pCount;
			}
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr == sp._ptr) //判断指向的资源是不是同一块资源
			{
				return *this;
			}

			Release();

			_ptr = sp._ptr;
			_pCount = sp._pCount;

			(*_pCount)++;

			return *this;
		}

整体模拟实现如下:

	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			: _ptr(ptr)
			, _pCount(new int(1)) // 只有在对象调用构造函数的时候,才会新建一个管理资源的引用计数
		{}

		shared_ptr(const shared_ptr<T>& sp)
			: _ptr(sp._ptr)
			, _pCount(sp._pCount) // 拷贝构造的时候不新建引用计数,只拷贝资源指针和计数指针,并且计数++
		{
			*(_pCount)++;
		}

		T* operator->()
		{
			return _ptr;
		}

		T& operator*()
		{
			return *_ptr;
		}

		void Release()
		{
			if (--(*_pCount) == 0)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
				delete _pCount;
			}
		}

		~shared_ptr()
		{
			Release();
		}
		
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr == sp._ptr) //判断指向的资源是不是同一块资源
			{
				return *this;
			}

			Release();

			_ptr = sp._ptr;
			_pCount = sp._pCount;

			(*_pCount)++;

			return *this;
		}

		int use_count()
		{
			return *_pCount;
		}

		T* get()
		{
			return _ptr;
		}

	private:
		T* _ptr;

		//引用计数
		int* _pCount;
	};

2.shared_ptr的循环引用

在这里插入图片描述
如上图所示,这种情况下,左右两个节点都由shared_ptr n1、n2管理,节点中的指针同样也是shared_ptr,左边节点的next指向右边节点,右边节点的prev指向左边节点,也就是next管着右边节点的内存块,prev管着左边节点的内存块,那么左右两个节点的引用计数都是2;
如果仅仅析构n1和n2,那么左右两节点的引用计数都会减为1,不会触发资源的回收,因为还有next和prev在指向着资源;只有在左边节点被释放,调用了析构函数时,next才会被释放,右边节点的引用计数才能减到0,才能够释放,显然无法直接释放左边的节点;

为了解决shared_ptr的循环引用问题,c++11引入了weak_ptr

五、weak_ptr

weak_ptr不是常规的智能指针,没有RAII,不直接管理资源,weak_ptr主要用shared_ptr构造,用来解决shared_ptr的循环引用问题;
在这里插入图片描述
weak_ptr访问资源时,不增加引用计数,将节点成员的指针next和prev设置成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(const weak_ptr<T>& wp)
			: _ptr(wp._ptr)
		{}

		weak_ptr<T>& operator=(const weak_ptr<T>& wp)
		{
			_ptr = wp._ptr;
			return *this;
		}

		T* operator->()
		{
			return _ptr;
		}

		T& operator*()
		{
			return *_ptr;
		}

	private:
		T* _ptr;
	};

六、定制删除器

如果自定义类型使用new [ ]申请空间,使用delete释放可能会出错;
在这里插入图片描述
delete[]是在开空间时多开4byte的空间在头部,储存new的对象的个数,在delete[]的时候就知道要调用多少次析构函数了;
shared_ptr和unique_ptr都支持定制删除器

在这里插入图片描述
shared_ptr需要在调用构造函数初始化时传一个仿函数对象或者lambda表达式;
unique_ptr需要传模板参数为仿函数;

template<class T>
struct DeleteArray
{
	void operator()(T* ptr)
	{
		cout << "delete:" << ptr << endl;
		delete[] ptr;
	}
};

template<class T>
struct Free
{
	void operator()(T* ptr)
	{
		cout << "delete:" << ptr << endl;
		free(ptr);
	}
};

int main()
{
	//调用构造函数时传仿函数对象
	std::shared_ptr<int> n1(new int[5], DeleteArray<int>());
	std::shared_ptr<int> n2((int*)malloc(5 * sizeof(int)), Free<int>());

	//调用构造函数时传lambda表达式
	std::shared_ptr<int> n3(new int[5], [](int* ptr) {delete[] ptr; });

	//unique_ptr在模板参数中传仿函数
	std::unique_ptr<int, DeleteArray<int>> n4(new int[5]);

	return 0;
}

七、内存泄漏

1.什么是内存泄漏

内存泄漏是指因为疏忽或错误导致程序未能释放已经不再使用的内存的情况;内存泄漏不是内存在物理上消失,而是因为设计错误而失去了对内存的控制(指针丢失);
如果内存还在,进程正常结束,内存也会释放;

2.内存泄露的危害

长期内存泄漏,将导致程序相应越来越慢,直到卡死;

3.如何避免内存泄漏

(1)养成良好的工程编码规范,申请的内存记着去释放;
(2)使用RAII的思想或者智能指针来管理资源;
(3)使用内存泄漏检测工具;

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

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

相关文章

PDF转Word免费的软件有哪些?(完整教程版!)

这五种免费PDF转Word的方式&#xff0c;不仅免费&#xff0c;还能准确恢复PDF文件内容&#xff0c;前方高能&#xff0c;快速学习&#xff01; 1.Office直接打开 大厂软件&#xff0c;既能读文档又能转换格式&#xff0c;总让人放心。 转换流程1&#xff1a;在Word的最新版本…

Zero系列三部曲:Zero、Zero-Offload、Zero-Infinity

Zero系列三部曲&#xff1a;Zero、Zero-Offload、Zero-Infinity ZeroIntroductionZero DP流程图详解 Zero-R Zero-OffloadZero- Infinityreference Zero Introduction 以数据并行为例&#xff0c;在训练的时候&#xff0c;首先把模型参数在每个GPU上复制一份&#xff0c;然后…

IP报文解析(TCP、UDP、 ICMP)及代码分享(C++)

一、OSI模型与TCP/IP协议栈 1.1 OSI 7层模型&#xff1a; 应用层&#xff1a; 功能&#xff1a;用户接口&#xff0c;文件传输、电子邮件、虚拟终端、文件服务 设备&#xff1a;网关 协议&#xff1a;HTTP、TFTP、SMTP、FTP、SNMP、DNS、Telnet 表示层&#xff1a; 功能&…

Spring 面试题总结(2023最新版)

文章目录 1、谈谈你对Spring的理解&#xff1f;1.1 发展历程1.2 Spirng的组成1.3 Spring的好处 2、Autowired和Resource的区别2.1 共同点&#xff1a;2.2 Autowired2.3 Resource2.3.1 Resource的装配顺序 3、Spring常用注解3.1、给容器中注入组件3.1.1 包扫描组件标注注解3.1.2…

复数的基本知识

复数的基本知识 文章目录 复数的基本知识前言表示方法百度百科简单来说&#xff1a; 复数的运算 前言 这里只有一点点关于复数的知识&#xff0c;主要是最近的FFT要用到。 表示方法 百度百科 我们把形如 abi &#xff08;a,b均为实数&#xff09;的数称为复数&#xff0c;其…

c++ “拷贝构造,运算符重载”

1.拷贝构造 拷贝构造的意思为&#xff1a;创造一个对象并拷贝另一个对象. 拷贝构造的名字与类的名字一致&#xff0c;参数一般为引用&#xff0c;若类中为无拷贝构造&#xff0c;编译器会自动生成默认拷贝构造函数&#xff0c;这种默认的拷贝构造函数只能拷贝内容&#xff0c;不…

安装并使用JupyterLab

背景 JupyterLab 是用于笔记本、代码和数据的最新的基于 Web 的交互式开发环境。其灵活的界面允许用户配置和安排数据科学、科学计算、机器学习方面的工作流程。模块化设计邀请扩展来扩展和丰富功能。 部署 要在本机搭建 Jupyter Notebook 平台进行机器学习项目&#xff0c;…

【Python入门篇】——Python基础语法(字符串扩展和格式化的精度控制)

作者简介&#xff1a; 辭七七&#xff0c;目前大一&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; Python入门&#xff0c;本专栏主要内容为Python的基础语法&#xff0c;Python中的选择循环语句…

网络安全之IPSEC路由基本配置

目录 网络安全之IPSEC路由基本配置 IPSEC配置的前提分析 协议分析 传输模式分析​编辑 IPSEC路由中的配置 图谱图 配置公网可达 R1配置IKE SA的安全提议 R1配置 IKE SA 的身份认证信息 R3配置IKE SA的安全提议 R3配置 IKE SA 的身份认证信息 R1配置IPSEC的安全提议…

112-Linux_mysql数据库的安装

文章目录 一.数据库介绍1.数据库2.数据库的分类 二.mysql安装及设置1.安装mysql2.初始化配置(1)设置数据库不进行密码强校验(2)设置root管理员密码(3)设置是否要删除匿名用户&#xff0c;这里不删除(4)设置是否允许root用户远程登录&#xff0c;这里设置允许(5)是否删除test库&…

硬件设计 之 PCIe常用知识

以下是本人在自己在设计PCIe中常遇到的一些知识&#xff0c;对他们进行了简单整理一下&#xff0c;包括基本定义、传输速率、layout要求等。比如作为硬件工程师要了解芯片架构&#xff0c;哪些PCIe接口可以使用&#xff0c;使用这些PCIe要做什么&#xff0c;需要使用PCIe x1还是…

msvc编译opencascade和vtk

文章目录 msvc编译opencascade和vtk下载源码和第三方库开始编译VTK开始编译OCC msvc编译opencascade和vtk 下载源码和第三方库 opencascade源码 我下载的时候最新版本是7.7.0 第三方库文件 这里面标出来的是必须的文件&#xff0c;如果你需要别的&#xff0c;也可以下载其他的…

JNI开发

文件结构&#xff08;选中的为生成的&#xff09; CMake构建不需要执行命令&#xff0c;会自动生成so文件打包进apk Android mk构建需要执行命令生成so文件&#xff0c;再打包进apk。命令如下。 # 在jni目录下执行 # 生成com_demo_cppproject_OtherNdkTest.h头文件 javac -h .…

[MySQL / Mariadb] 数据库学习-Linux中二进制方式安装MySQL5.7

Linux中二进制方式安装MySQL5.7 安装安装方式官网下载安装包创建用户组mysql&#xff0c;用户和目录把下载的安装包&#xff0c;放到/home/mysql/将本地文件拷贝到远程&#xff1a; scp 文件名 –用户名计算机IP或者计算机名称:远程路径 验证包解压安装包&#xff0c;移动到/us…

【前端面试题】深拷贝的终极实现

大厂面试题分享 面试题库 前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 web前端面试题库 VS java后端面试题库大全 引子 通过本文可以学习到深拷贝的三种写法的实现思路与性能差异 首先&#xff0c;我们要理…

Hystrix 简单聊聊断路器/熔断器

本文首发自「慕课网」&#xff0c;想了解更多IT干货内容&#xff0c;程序员圈内热闻&#xff0c;欢迎关注"慕课网"&#xff01; 作者&#xff1a;风间影月|慕课网讲师 什么是Hystrix Hystrix 在SpringCloud中负责服务熔断服务降级的作用。 Hystrix 存在的目的也是…

从 Elasticsearch 到 Apache Doris,10 倍性价比的新一代日志存储分析平台

作者介绍&#xff1a;肖康&#xff0c;SelectDB 技术副总裁 导语 日志数据的处理与分析是最典型的大数据分析场景之一&#xff0c;过去业内以 Elasticsearch 和 Grafana Loki 为代表的两类架构难以同时兼顾高吞吐实时写入、低成本海量存储、实时文本检索的需求。Apache Doris…

114-Linux_mysql基本操作

文章目录 一.数据库的基本操作1.数据库的登录及退出(1)连接数据库&#xff1a;(2)退出数据库 2.查看所有数据库3.显示时间4.显示数据库版本5.创建数据库6.查看创建数据库的语句7.查看当前使用的数据库8.查看当前用户9.使用某个数据库10.删除数据库 一.数据库的基本操作 1.数据…

训练计划安排(练一休一训练分化+倒金字塔训练法)

目录 练一休一训练分化每次训练的组数12-15组 &#xff08;4-5个动作&#xff09;QA 倒金字塔训练法倒金字塔热身正式组常见误区&#xff1a; 训练补剂bcaa咖啡因肌酸蛋白粉 如何降低皮质醇水平如何提升睾酮水平文献出处睡眠8h摄入适量脂肪&#xff08;0.8g每公斤体重&#xff…

【机器学习】集成学习—Boosting—GBM(Gradient Boosting Machine)解析

【机器学习】集成学习—Boosting—GBM&#xff08;Gradient Boosting Machine&#xff09;解析 文章目录 【机器学习】集成学习—Boosting—GBM&#xff08;Gradient Boosting Machine&#xff09;解析1. 介绍2. Boosting2.1 1. 强 / 弱学习器2.1.2 AdaBoost 3. GBM3.1 GBM 特例…