C++ - 智能指针的定制删除器

news2024/11/19 2:35:33

前言

在上一篇C++ 文章当中,对 智能指针的使用,做了很详细的介绍,对 C++11 和 C++98 库当中实现的一些常用智能指针做了很详细的介绍,但是智能指针的使用还有一些拓展用法。上篇文章链接:
C++ - 智能指针 - auto_ptr - unique_ptr - std::shared_ptr - weak_ptr-CSDN博客

 智能指针定制删除器

我们再用 智能指针来管理空间的时候,对于这块空间的开辟方式有很多,比如:malloc ()函数,fopen 文件对象,new 堆上开辟,还有 new 开辟一个 数组;这些不同方式开辟的空间对于这些空间的释放方式是不一样的。

所以,我们再删除这些动态开辟的空间的时候,就不能直接一种方式来实现,要实现多种方式,使用者自己控制 释放空间的方式的话,这种实现其实在之前已经说过很多了,就是使用仿函数 ,lambda表达式 ,函数指针的方式来实现,但是,函数指针是不推荐使用的。

对于仿函数的使用可以参考,下述博客当中,红黑树的 迭代器在map 和 set 当中的使用:

C++ - 红黑树 介绍 和 实现-CSDN博客h

还有 下述对 优先级队列当中对 比较方式 的仿函数书写:

C++ - 优先级队列(priority_queue)的介绍和模拟实现 - 反向迭代器的适配器实现 - 仿函数_c++ priority_queue迭代器_chihiro1122的博客-CSDN博客

如下我们就用仿函数来实现,对 new 出来的数组空间释放:

template<class T>
struct freearray
{
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};

仿函数非常好实现,delete[] 就可以帮助我们释放数组空间。

这里不再使用 :再 shared_ptr 的函数模版参数当中传入仿函数类型,在shared_ptr 的成员函数当中创建一个 仿函数对象的方式,利用仿函数对象来调用 其中的operator()函数的方式来使用。

此时,如果是 库 当中的 shared_ptr 的话,我们就可以直接这样来控制 释放方式:

	std::shared_ptr<A> st(new A[10], freearray<A>());

 输出:

A(int a = 1)
A(int a = 1)
A(int a = 1)
A(int a = 1)
A(int a = 1)
A(int a = 1)
A(int a = 1)
A(int a = 1)
A(int a = 1)
A(int a = 1)
delete:00000278A2EBA8A8
~A()
~A()
~A()
~A()
~A()
~A()
~A()
~A()
~A()
~A()

 当然,还可以使用 lambda 表达式来实现:

	std::shared_ptr<A> st2((A*)malloc(sizeof(A)), [](A* ptr) {free(ptr); });

在 shared_ptr 内部是如何实现 上述的,其实是在 shared_ptr 类当中写了一个 构造函数的模版,这个构造函数模版可以多接收一个 对象,这个对象在外部就可以传入一个 仿函数类匿名对象,这个对象就可以帮助我们调用其中的 operator()函数。

但是,如果是传入 一个 lambda 表达式的话,这个表达式返回的是一个 lambda+uuid 为名字的 类对象,这个对象当中也重载了 operator()函数,所以,关于传入的 是仿函数 还是一个 lambda 表达式,都是可以像使用 operator() 的方式一样访问的。

但是,问题来了:operator()函数的调用,是需要一个类对象作为媒介的,也就是说,不管是 仿函数 还是  lambda 表达式,都是需要创建一个对象来进行调用的。

但是,我们外部传入的对象是在 构造函数的基础之上来进行传入的,但是 空间的释放操作是在 析构函数当中进行的,如何接收 构造函数的对象,传入 析构函数呢?

你肯定已经想到了,就是使用 成员变量,我们可以创建一个 成员变量,这个变量用于在构造函数当中接收 仿函数类对象 或者 lambda 返回的对象。

但是,这个成员变量的类型如何定义呢?我们在构造函数 当中用于接收对象的 形参的类型是 这个 构造函数 的一个 模版参数,但是在类当中定义一个 成员变量是需要具体类型或者是 类模版参数的,函数模版参数是不能用于构造 类 的成员变量的:

 我们不能用构造函数当中的 D 这个函数模版参数来构造 shared_ptr 当中的成员变量。

这里,我们可以使用包装器实现。包装器,可以把 仿函数对象,lambda 表达式的返回值,函数指针,包装成一个 function 类对象,我们就可以直接使用这个类对象来调用其中的 仿函数对象的operator(),lambda 表达式, 或者是 函数指针:
 

private:

    function<void(T*)> _del = [](T* ptr) { delete[] ptr; };

如上述所示:_del 这个成员变量就是我们想要的  ,用于接收对象的成员变量了。

这个 _del 有一个 缺省参数,是一个 lambda表达式的对象,在这个 lambda 表达式当中实现了 对于 new 数组的空间释放操作,也就是说,如果你不传入 shared_ptr 构造函数的第二个参数(释放空间的方法)的话,就默认是 使用 new 数组空间的释放方法。

完整代码(+测试例子):
 

class A
{
public:
	A(int a = 1)
		:_a(a)
	{
		cout << "A(int a = 1)" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}

	int _a;
};


namespace My_shared_ptr
{
	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		// 像指针一样
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))
		{}

		template<class D>
		shared_ptr(T* ptr, D del)
			: _ptr(ptr)
			, _pcount(new int(1))
			, _del(del)
		{}

		~shared_ptr()
		{
			if (--(*_pcount) == 0)
			{
				cout << "delete:" << _ptr << endl;
				_del(_ptr);
				delete _pcount;
			}
		}

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

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

		// sp3(sp1)
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			++(*_pcount);
		}

		// sp1 = sp5
		// sp6 = sp6
		// sp4 = sp5
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			// 先判断 赋值和被赋值的两个指针是否是重复的
			// 是就直接返回
			if (_ptr == sp._ptr)
				return *this;

			// 判断当前 赋值指针在赋值出去之前
			// 是否是所维护空间的唯一指针
			if (--(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;
			}

			// 开始赋值
			_ptr = sp._ptr;
			_pcount = sp._pcount;

			// 引用计数++
			++(*_pcount);

			return *this;
		}

		// 返回引用计数
		int use_count() const
		{
			return *_pcount;
		}

		// 拿到原生指针
		T* get() const
		{
			return _ptr;
		}

	private:
		T* _ptr;
		int* _pcount;

		function<void(T*)> _del = [](T* ptr) { delete[] ptr; };
	};

}

template<class T>
struct freearray
{
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};

int main()
{
	//My_shared_ptr::shared_ptr<A> st(new A[10], freearray<A>());
	std::shared_ptr<A> st2((A*)malloc(sizeof(A)), [](A* ptr) {free(ptr); });

	return 0;
}

内存泄漏问题

关于内存泄漏,在之前的博客当中已经说明得很清楚了,简单来说就是,《占着茅坑不拉shi》 (比喻有点恶心,但是非常形象)。

系统当中的某些变量,在之后的程序进行当中都不会使用了,但是,这些变量就是没有被释放,这就造成了一种内存泄漏。

这种内存泄漏问题,在我们日常编写代码的过程当中是体会不出来的,最典型的就是在服务器当中,因为所谓服务器,就是写有强大操作系统,等等比较好的硬件设施的,就可以通过计算机网络,给主机来共享数据,从而使得我们可以访问其中的数据,甚至于让我们使用到 服务器当中的操作系统。

这样的好处就在于,服务器的操作系统一般是非常牢固的,我们经常使用的服务器都是大公司维护,更加安全。

我们主机在使用的时候,就可以不用关系自己的操作系统是否牢固,直接可以通过网络和服务器连接,使用到服务器的强大操作系统。

这样来说的话,服务器的就是一直被动接受的状态,他需要时时接受每一个用户给出的相应(请求),所以服务器一般是一直开启的,关闭维修的情况都是服务器出现了重大错误,比如说:内存泄漏。

如何是一个泄漏很多空间 的 内存泄漏 是内存泄漏当中比较好的情况,因为当你服务器以 运行,就会很快的 内存泄漏,抛出你写的异常;但是如果是 一次 只是泄漏 1M ,很小的数字,那么,你只会感觉是服务器越来越卡,当有一天,突然抛出内存泄漏的异常,你是需要去查看日志的,时间过去这么久,日志该怎么查,代码量这么多,相信你已经感受到了。

具体可以看一下博客,对内存泄漏的详细介绍:
C/C++ 内存管理 new delete operator new与operator delete函数 内存泄漏-CSDN博客

 像上述的 智能指针 也可以 很大帮助我们 提前预防 内存泄漏,帮助我们在最后释放空间。

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

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

相关文章

Stm32_标准库_16_串口蓝牙模块_手机与蓝牙模块通信_手机传入信息能对芯片时间日期进行更改

实现了手机发送信息给蓝牙模块&#xff0c;程序对数据进行分析拆解&#xff0c;并更新自身数据 main.c: #include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "Serial.h" #include "Ti…

无人驾驶路径规划(一)全局路径规划 - RRT算法原理及实现

前言&#xff1a;由于后续可能要做一些无人驾驶相关的项目和实验&#xff0c;所以这段时间学习一些路径规划算法并自己编写了matlab程序进行仿真。开启这个系列是对自己学习内容的一个总结&#xff0c;也希望能够和优秀的前辈们多学习经验。 一、无人驾驶路径规划 众所周知&a…

Google Authenticator 和gitlab使用的方法配置Google AuthenticatorGoogle

Google Authenticator 和gitlab使用的方法 目录概述需求&#xff1a; 设计思路实现思路分析1.配置过程&#xff1a; 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a bette…

D201126 D201129 支持以太网高级物理层(APL)

D201126 D201129 支持以太网高级物理层(APL) 全球技术和软件领导者艾默生宣布了基于其无限自动化愿景&#xff0c;并作为其下一代以软件为中心的工业自动化架构的基础。新技术的发布将超越传统的控制系统&#xff0c;创建一个更先进的自动化平台&#xff0c;为人类和塑造世界…

【网络】网络层协议:IP(待更新)

文章目录 IP 协议1. 基本概念2. IP 报头解析 &#x1f53a;IP 的 网段划分&#xff1a; IP 协议 IP 不是面向字节流的&#xff0c;而是发送接收一个个的数据包 1. 基本概念 主机&#xff1a;配有 IP 地址的设备 路由器&#xff1a;配有单个或多个 IP 地址&#xff0c;且能进行…

【1314. 矩阵区域和】

目录 一、题目描述二、算法思想三、代码实现 一、题目描述 二、算法思想 三、代码实现 class Solution { public:vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {//先预处理数组int nmat.size();//行int mmat[0].size();…

flutter开发实战-下拉刷新与上拉加载更多实现

flutter开发实战-下拉刷新与上拉加载更多实现 在开发中经常遇到列表需要下拉刷新与上拉加载更多&#xff0c;这里使用EasyRefresh&#xff0c;版本是3.3.21 一、什么是EasyRefresh EasyRefresh可以在Flutter应用程序上轻松实现下拉刷新和上拉加载。它几乎支持所有Flutter Sc…

C++指针和引用

1、引用必须初始化&#xff0c;指针不必&#xff0c;所以说引用使你更安全的指针&#xff1b; 2、在汇编代码&#xff0c;指针和引用一模一样&#xff1b; 3、引用只有一级引用&#xff0c;没有多级引用&#xff1b; 4、引用必须引用一个能取地址的变量&#xff1b; 左值&…

第三章 内存管理 五、动态分区分配算法(首次适应算法、最佳适应算法、最坏适应算法、临近适应算法)

目录 一、首次适应算法 1、算法思想&#xff1a; 2、如何实现&#xff1a; 3、两种常用的数据结构: &#xff08;1&#xff09;空闲分区表、空闲分区链 4、例子 二、最佳适应算法 1、算法思想: 2、如何实现: 3、例子&#xff1a; 三、最坏适应算法 1、算法思想&…

蓝桥杯每日一题2023.10.16

数的分解 - 蓝桥云课 (lanqiao.cn) 题目描述 题目分析 最开始想使用dfs&#xff0c;发现范围过大无法在规定时间运行 #include<bits/stdc.h> using namespace std; const int N 2e5 10; int a[N], v[N], ans; void dfs(int dep, int sum, int start) {if(sum > 20…

Linux命令行下查看实时网速

Linux命令行下&#xff0c;用ifconfig可以看到每个网卡实时的收发的数据包了字节数&#xff0c;但并不方便查看当前网卡的实时网速。 近日偶得一个软件叫nload可以方便的在命令行下查看实时网速&#xff0c;不敢独享&#xff0c;分享给大家。 1、安装 sudo apt install nload…

第三章 内存管理 六、基本分页存储管理

目录 一、定义 二、例子 三、总结 一、定义 基本分页存储管理是一种操作系统的存储管理技术。在基本分页存储管理中&#xff0c;物理内存被划分成固定大小的块&#xff0c;称为页面&#xff08;Page&#xff09;&#xff0c;而程序代码和数据被分成相同大小的块&#xff0c;…

数据结构:二叉树(1)

目录 树的概念 树的表示形式 二叉树 二叉树的性质 题目 二叉树的存储 链式存储 初始化二叉树 二叉树的遍历 前序遍历&#xff1a;根&#x1f449;左子树&#x1f449;右子树 中序遍历&#xff1a;左子树&#x1f449;根&#x1f449;右子树 后序遍历&#xff1a;左子…

二十八、【滤镜】

文章目录 滤镜库Camera Raw滤镜神经网络滤镜(Neurel Filters)液化其他滤镜 滤镜库 可以在滤镜库中选择一些常用的滤镜方式&#xff0c;主要有六大类&#xff1a; Camera Raw滤镜 Camera Raw滤镜是photoshop最重要的一个滤镜&#xff0c;他帮助我们在摄影后期去进行颜色预处…

导数、偏导数、方向导数

一、导数 导数是描述函数变化率的数学概念。 导数的定义式: 那么就有一个问题&#xff0c;为什么求函数的最大值点是要对其自变量求导&#xff0c;并使其导数等于0 比如:求L(θ)3lnθ2ln(1-θ)的最大值点 既然导数表示函数的变化率&#xff0c;那么当函数L(θ)的导数等于0&…

Go语言基础之包

包&#xff08;package&#xff09; Go语言中支持模块化的开发理念&#xff0c;在Go语言中使用包&#xff08;package&#xff09;来支持代码模块化和代码复用。一个包是由一个或多个Go源码文件&#xff08;.go结尾的文件&#xff09;组成&#xff0c;是一种高级的代码复用方案…

构建高性能物联网数据平台:EMQX和CnosDB的完整教程

CnosDB 是一款高性能、高压缩率、高易用性的开源分布式时序数据库。主要应用场景为物联网、工业互联网、车联网和IT运维。所有代码均已在GitHub开源。本文将介绍如何使用EMQX 这一MQTT 服务器 CnosDB 构建物联网数据平台&#xff0c;实现物联网数据的实时流处理。 前言 在物联…

CLIP和改进工作

CLIP和改进工作 CLIP 改进方向 语义分割 Lseg、GroupViT 目标检测 ViLD、GLIP v1/v2 视频理解 VideoCLIP、CLIP4clip、ActionCLIP 图像生成 VQGAN-CLIP、CLIPasso、CLIP-Draw 多模态下游任务 VL Downstream 其他 prompt enginering&#xff08;CoOp等&#xff09; depthCLIP、…

(C++ STL) 详解vector模拟实现

目录 一.vector的介绍 1.vector的介绍 二.vector的定义模拟实现 三.vector各接口的模拟实现 1.vector迭代器的模拟实现 2.构造函数 2.1无参构造 2.2 n个val构造 2.3迭代器区间构造 2.4通过对象初始化&#xff08;拷贝构造&#xff09; 3.析构函数 4.size 5.operato…

PCB沉金包边工艺流程与主要作用经验总结

🏡《总目录》 目录 1,什么是PCB沉积包边2,PCB沉金包边作用2.1,射频屏蔽2.2,EMC认证2.3,防氧化2.4,强电屏蔽2.5,美观3,PCB沉金包边的工艺流程4,总结1,什么是PCB沉积包边 PCB沉金包边是指,在PCB的侧边也包裹上铜皮,并在铜皮的表面进行沉金工艺。在高频电路板中经常…