【1++的C++进阶】之智能指针

news2024/11/25 9:53:03

👍作者主页:进击的1++
🤩 专栏链接:【1++的C++进阶】

文章目录

  • 一,什么是智能指针
  • 二,为什么需要智能指针
  • 三,智能指针的发展

一,什么是智能指针

要了解智能指针,我们先要了解RAII.
RAII是一种利用对象生命周期来控制资源的技术。
在对象初始化时,其接管资源,在对象的生命周期内其管理的资源始终保持有效,最后当对象析构时,释放资源。

那么什么是智能指针呢?
智能指针就是利用了RALL的原理,并且通过封装,使得它的对象能够向指针一样使用。因此其重载了*,->。

下面是一个简单的智能指针代码:

template<class T>
	class smart_ptr
	{
	public:
		smart_ptr(T* ptr)
			:_ptr(ptr)
			{}
		
		~smart_ptr()
		{
			if (_ptr)
			{
				delete _ptr;
			}

		}

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

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

	private:
		T* _ptr;
	};

二,为什么需要智能指针

我们来看一段代码:

double divide(int a, int b)
{
	if (b == 0)
		throw "除0错误";
	else
		return a / b;
}
void func()
{
	int* arr = new int[10];//申请空间
	try
	{
		int a, b;
		cin >> a >> b;
		divide(a, b);
	}
	catch (...)
	{
		//在这里进行校对后,再抛出
		delete[]arr;
		cout << "delete[]arr" << endl;
		throw;
	}

	delete []arr;//若出现异常,则不会执行到这里,会造成内存泄漏。
	cout << "delete[]arr" << endl;

}
int main()
{
	try
	{
		func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (...)
	{
		cout << "错误" << endl;
	}
	return 0;
}

上述代码中,我们为了防止内存泄漏,因此进行了校正,再抛出的操作,这样做相对来说比较麻烦,而且我们在写代码时也是容易忘记校正的操作,这时就需要我们的智能指针了。我们在申请资源的函数内部实例化除一个智能指针的对象,将资源交给它管理,当这个函数调用完成退出后,这个对象也会进行析构,其管理的资源也就释放了。是不是相当方便。

上述提到了内存泄漏,接下来我们就专门来谈一谈内存泄漏。
什么是内存泄漏呢?
内存泄漏是指由于我们的失误,未能释放我们已经不适用的内存,而造成资源的浪费。也可以说是,我们由于设计错误,而对该段内存失去了控制权,进而造成了空间的浪费。

内存泄漏分类:

  1. 堆内存泄漏(Heap leak)
    堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak
  2. 系统资源泄漏
    指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

三,智能指针的发展

在我们前面写的智能指针中我们会发现其不能够拷贝构造和赋值。因为若我们用默认生成的拷贝构造或赋值的话,由于两个指针指向同一块空间,那么这块空间在释放时就会被释放两次而导致错误。

因此在C++98中就有了auto_ptr的出现,它在面对拷贝构造和赋值问题的解决办法是:管理权的转移-----也就是:当通过A构造出或将A赋值给B时,A的管理权转让给B,A不再进行管理。
这样的设计在后来经常为人所诟病。因为其在拷贝或赋值后,原来的对象指向的空间会被置空,也就是失去了管理权。
下面是其实现代码:

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

		auto_ptr(auto_ptr& s)
		{
			_ptr = s._ptr;
			s._ptr = nullptr;
		}

		auto_ptr& operator=(auto_ptr s)
		{
			if (s._ptr != this->_ptr)
			{
				delete _ptr;
				_ptr = s._ptr;
				s._ptr = nullptr;

			}
			return *this;
		}

		~auto_ptr()
		{
			if (_ptr)
			{
				delete _ptr;
			}

		}

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

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

		T* get()
		{
			return _ptr;
		}

	private:
		T* _ptr;
	};

由于auto_ptrd的缺陷,在C++11中出现了unique_ptr。
unique_ptr的的原理非常粗暴----既然拷贝和赋值是个坑,那我直接禁用你。。。
因此在unique_ptr中,其主要改变就是禁用了拷贝和赋值。
禁用拷贝和赋值有下面几种方法:

  1. 成员函数私有化。
  2. 只声明不定义
  3. 用delete关键字修饰。

下面是unique_ptr的实现:

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

		unique_ptr(unique_ptr& s) = delete;


		unique_ptr<T>& operator=(unique_ptr s) = delete;


		~unique_ptr()
		{
			if (_ptr)
			{
				delete _ptr;
			}

		}

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

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

		T* get()
		{
			return _ptr;
		}

	private:
		T* _ptr;
	};

但是,在有些场景下两个对象管理同一块空间的这种需求还是有的,因此C++11中还出现了更加靠谱的shared_ptr。
其原理就是增加了引用计数。对于同一块资源,每赋值或拷贝一次,计数就加1 。每析构一个对象就减一。直到为0也就是只有一个对象在维护着这块空间时,其就释放这块资源。

下面是shared_ptr的实现:


template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr)
			:_ptr(ptr)
			, count_ptr(new int(1))
		{
		}

		shared_ptr(const shared_ptr& s)
			:_ptr(s._ptr)
		{
			(*s.count_ptr)++;
			count_ptr = s.count_ptr;

		}

		shared_ptr<T>& operator=(const shared_ptr& s)
		{
			if (_ptr != s._ptr)
			{
				_ptr = s._ptr;
				(*s.count_ptr)++;
				count_ptr = s.count_ptr;
			}

			return *this;
		}

		~shared_ptr()
		{
			if (*count_ptr > 1)
			{
				(*count_ptr)--;
			}
			else
			{
				delete _ptr;
				
			}

		}

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

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

		T* get()const
		{
			return _ptr;
		}

	private:
		T* _ptr;
		int* count_ptr;
	};

但是,shared_ptr也是有一些问题的。

  1. 线程安全问题----我们在后面的文章会进行讲解。
  2. 循环引用问题和对象的删除问题

接下来我们讲 2 中的两个问题。
循环引用问题:
我们先来看看什么是循环引用问题:
在这里插入图片描述
如图,就是我们的循环引用,其到底会产生什么问题呢?
我们慢慢往下看。
当我们node1,node2析构后,其计数都为1,还并没有释放掉。只有_next释放掉,node2才能释放,_prev释放掉,node1才能释放。而只有node1释放掉_next才能释放,node2释放,_prev才能释放。
这就形成了一个死循环。谁的释放不了。
因此引入了weak_ptr。
其就是用来解决循环引用问题的。
其解决循环引用的原理就是:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr类型。node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和_prev不会增加node1和node2的引用计数。
这样在析构node1和node2时,其管理的资源也都进行了释放。

下面是weak_ptr的实现代码:


	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{
		}

		weak_ptr(const shared_ptr<T>& s)
			:_ptr(s.get())
		{
		}

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

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

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

	private:
		T* _ptr;

	};

我们再来解决删除的问题:
我们在《内存管理》这篇文章中讲过new 和delete要搭配使用,new …[n]要与delete [] …,malloc与free搭配…
并且我们讲了new和malloc的区别,delete与free的区别。
那么new 与new[] ,delete与delete[]有什么区别呢?
下面我们来用一张图讲解清楚:

在这里插入图片描述
可见delete与delete[]区别在于调用析构函数的次数和释放空间的指针的位置。因此,当我们用delete去释放一个本该用delete[]释放的对象时,便会发生错误。

我们也不能去修改库里的析构函数。那我们该怎么解决呢?
在这里插入图片描述

C++11中给我们提供了在构造智能指针对象时可以传一个删除器的对象来应对不同方式申请的对象。
下面我们用仿函数的形式进行模拟:
代买实现如下:

template<class T,class D>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr)
			:_ptr(ptr)
			, count_ptr(new int(1))
		{
			cout << "构造" << endl;
		}

		shared_ptr(const shared_ptr& s)
			:_ptr(s._ptr)
		{
			(*s.count_ptr)++;
			count_ptr = s.count_ptr;
			cout << "拷贝构造" << endl;

		}

		shared_ptr<T,D>& operator=(const shared_ptr& s)
		{
			if (_ptr != s._ptr)
			{
				_ptr = s._ptr;
				(*s.count_ptr)++;
				count_ptr = s.count_ptr;
				cout << "赋值" << endl;
			}

			return *this;
		}

		~shared_ptr()
		{
			if (*count_ptr > 1)
			{
				(*count_ptr)--;
				cout << "count--" << endl;
			}
			else
			{
				D()(_ptr);
				//cout << "delete" << endl;
			}

		}

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

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

		T* get()const
		{
			return _ptr;
		}

	private:
		T* _ptr;
		int* count_ptr;
	};


删除器
	template<class T>
	class Free
	{
	public:
		void operator()(T* ptr)
		{
			cout << "Free" << endl;
			free(ptr);
		}
	};

	template<class T>
	class Delete
	{
	public:
		void operator()(T* ptr)
		{
			cout << "Delete" << endl;
			delete ptr;
		}
	};

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

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

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

相关文章

Linux上运行Redis服务出现报错及解决方法

近期&#xff0c;有用户反馈在Linux上运行Redis服务时遇到了一个报错&#xff1a;“Sorry, target machine refused connection”。下面我们来分析这个报错的解决方法。 一、报错分析 该报错通常是由于Redis服务无法与目标机器建立连接导致的。可能的原因包括以下几个方面&…

IP模块组装网络包及转发网络包链路

引言 之前协议栈系列的文章讲解了 连接&#xff0c;收发网络包&#xff0c;断开连接这些操作协议栈模块的处理&#xff0c;但是协议栈是上层 接下来会 委托ip模块进行真正的处理。 网络包 网络包的组成 网络包由头部的控制信息和头部后面的传输数据组成。 控制信息代表了包要…

TikTok矩阵玩法:如何最大程度地利用平台资源

在数字时代&#xff0c;TikTok已经成为全球范围内数亿用户的创意天堂&#xff0c;不仅仅是一个娱乐平台&#xff0c;还是一个创收的宝地。 TikTok矩阵玩法的崛起正在引领创作者们探索全新的变现方案&#xff0c;他们通过巧妙地利用平台资源&#xff0c;实现了前所未有的创收机…

为何网站一定要使用SSL证书

当您在浏览器中输入网址并按下回车键时&#xff0c;您是否曾想过您的个人信息和隐私是否会被窃取&#xff1f;在当今数字化的时代&#xff0c;网络安全问题越来越受到人们的关注。而SSL证书正是保护您的网站和用户信息安全的重要工具。 SSL证书是一种数字证书&#xff0c;它使用…

Unity之NetCode多人网络游戏联机对战教程(1)

文章目录 1.什么是NetCode2.安装NGO 1.什么是NetCode 官网链接&#xff1a;https://docs-multiplayer.unity3d.com/netcode/current/about/ Netcode for GameObjects&#xff08;NGO&#xff09;是专为Unity构建的高级网络库。它能够在网络会话中将GameObject和世界数据同时发…

unity打包后无法读取Excel解决方法

一、前言 最近几乎遇到了所有能遇到的unity读取Excel 的问题。 因为使用的是unity5.4&#xff0c;而且还是32位。所以出现各种问题在所难免。 废话不多说&#xff0c;现有的现象是&#xff1a;在unity的编辑器里可以完美运行&#xff0c;读取Excel不成问题&#xff0c;但是打包…

(JavaEE) 多线程基础3——多线程的代码案例 (单例模式, 阻塞队列,定时器)详解!!!

​​​​​​​ 目录 单例模式 什么是单例模式&#xff1f; —— “饿汉模式” —— “懒汉模式” ——懒汉模式-多线程版 ——懒汉模式-多线程版&#xff08;改进版&#xff09; 总结“懒汉模式”—— 多线程&#xff08;线程安全版&#xff09; 的要点 阻塞队列 什么…

[JAVAee]Spring项目的创建与基本使用

目录 Spring项目的创建 Spring中Bean对象的存储与获取 存储Bean对象 获取并使用Bean对象 getBean方法的重载 本文章介绍了Spring项目创建与使用的过程与一定的注意事项. Spring项目的创建 首先在IDEA中,新建一个Maven 第二步,在pom.xml中写入spring的依赖. pom.xml是mav…

l8-d21 域名解析与http服务器实现原理

一、域名解析gethostbyname函数 主机结构在 <netdb.h> 中定义如下&#xff1a; struct hostent { char *h_name; /* 官方域名 */ char **h_aliases; /* 别名*/ int h_addrtype; /* 地址族&#xff08;地址类型&#xff09; */ int h_l…

[JAVAee]SpringBoot配置文件

配置文件的介绍 配置文件当中记录了许多重要的配置信息,例如: 数据库的连接信息(用户的账户与密码)项目的启动端口第三方系统的调用密匙用于记录问题产生的日志 在spring框架中一些特定的框架会自动调用配置文件中的配置信息来运用. 配置文件中的属性也起到了类似全局变量的…

基于ROS环境的相机标定教程

一、参考资料 ROS学习——利用电脑相机标定 二、安装usb_cam驱动包 usb_cam - ROS Wiki GitHub - ros-drivers/usb_cam: A ROS Driver for V4L USB Cameras usb_cam包用于读取图像。 1. 源码安装usb_cam usb_cam用于实时SLAM&#xff0c;配合ROS平台使用。 1.1 下载usb_…

【Linux初阶】信号入门 | 信号基本概念+信号产生+核心转储

&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f; &#x1f36d;&#x1f36d;系列专栏&#xff1a;【Linux初阶】 ✒️✒️本篇内容&#xff1a;Linux信号的基本概念&#xff08;生活信号、技术信号、信号生命周期、信号的保存位置和发送本质&#xff09;&…

Pytorch-MLP-CIFAR10

文章目录 model.pymain.py参数设置注意事项运行图 model.py import torch.nn as nn import torch.nn.functional as F import torch.nn.init as initclass MLP_cls(nn.Module):def __init__(self,in_dim3*32*32):super(MLP_cls,self).__init__()self.lin1 nn.Linear(in_dim,1…

RFID自动识别技术在数控工具系统的应用

RFID是一种自动识别技术&#xff0c;最早是应用在二战中进行敌我侦察机的识别&#xff0c;但是随着民用通信技术的放开&#xff0c;近年来网络通信技术以及信息安全技术都取得了重大的发展&#xff0c;RFID技术也逐渐在民用领域应用。 RFID自动识别技术在数控工具系统的应用 1、…

浅谈PDM与MES系统集成

摘要&#xff1a; 目前MES在制造行业变得炙手可热&#xff0c;然而很多企业都忽视了数据的源头&#xff0c;MES作为生产执行的信息化系统&#xff0c;我们该如何让其在企业中成功的实施&#xff0c;发挥更大的作用&#xff0c;这还需要PDM系统的支撑。本文就PDM与MES集成进行简…

css前端面试题(三)

文章目录 1、可继承属性和不可继承属性字体系列属性文本系列属性元素可见性列表布局属性光标属性 2、link和import的区别3、css优化4、 CSS预处理器/后处理器是什么&#xff1f;为什么要使用它们&#xff1f;5、单行、多行文本溢出隐藏6、实现一个扇形7、实现一个自适应的正方形…

【Axure高保真原型】人物卡片多条件搜索案例

今天和大家分享人物卡片多条件搜索的原型模板&#xff0c;我们可以输入姓名或者选择部门、岗位来快速筛选出对应的人物信息卡片。那这个模板是用中继器制作的&#xff0c;所以使用也很方便&#xff0c;只需要在中继器表格导入图片和填写对应内容&#xff0c;即可自动生成交互效…

1600*A. LCM Challenge(数论 || 找规律)

解析&#xff1a; n<3&#xff0c;特判 n为奇数&#xff0c;则n、n-1、n-2必定互质&#xff0c;所以结果即为三者之和。 n为偶数&#xff0c; 不会严格证明原因&#xff0c;但是找找规律&#xff0c;是这样的...... #include<bits/stdc.h> using namespace std; #de…

ros----发布者和订阅者模型

话题模型&#xff1a; 如何自定义话题消息 1.定义msg文件 2.在package.xml中添加功能包依赖 <build_depend>message_generation</build_depend> <exec_depend>message_runtime</exec_depend>3.在CMakeList.txt文件中添加编译选项 4.编译生成语言的相…

网络工程师是干什么的?常见岗位有哪些?

网络工程师是做什么工作&#xff1f; 网络工程师能够从事计算机信息系统的设计、建设、运行和维护工作。一般来说&#xff0c;分硬件网络工程师和软件网络工程师两大类&#xff0c;硬件网络工程师以负责网络硬件等物理设备的维护和通信&#xff1b;软件网络工程师负责系统软件…