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

前言

在上一篇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博客

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

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chihiro1122

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值