【C++ 第二十章】模拟实现 shared_ptr(可以拷贝的智能指针)

在这里插入图片描述

本文主要讲解如果简单模拟实现库中的 shared_ptr
而不会过多的对库中的 shared_ptr 讲解



1. 初始版本

智能指针的基本框架

namespace my
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{}
		~shared_ptr() {
			delete _ptr;
			_ptr = nullptr;
		}

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

	private:
		T* _ptr;
	};
}



2. 添加 引用计数器


因为一个资源需要对应一个 计数器

不能设置成:

普通成员:一个对象就有一个,达不到共同管理一个资源的目的

静态成员:所有对象共用一个,但达不到 不同资源对应不同计数器 的目的


解决办法:

(1)可以在创建管理这块资源的第一个对象时,创建新的计数器

(2)同时将 计数器 存储在堆区:即 new int(1),使其不会随一个对象的释放而销毁


引用计数的规则

(1)如果引用计数是 0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
计数=0,则表示自己是最后一个人,可以释放空间了

(2)如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
计数不等于0,则表示自己不是是最后一个人,没有释放资源的权力




namespace my
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			: _pCount(new int(1))  // 将 引用计数 开辟在堆里面
		{}


		// 每次调用析构都 计数-1, 如果计数=0,说明该对象是最后一个管理该资源的对象,可以直接释放该资源
		~shared_ptr() {
			if (--(*_pCount) == 0) {
				delete _ptr;
				delete _pCount;
				_pCount = nullptr;
				_ptr = nullptr;
			}
		}

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

	private:
		T* _ptr;
		int* _pCount;  // 引用计数
	};
}



3. 添加 赋值重载 + 拷贝构造

实现思路:
拷贝构造:就是将自己的指针指向 传递过来的对象 管理的资源,同时 计数+1
赋值重载:思路差不多

namespace my
{
	template<class T>
	class shared_ptr
	{
		typedef shared_ptr Self;
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pCount(new int(1))
		{}

		// 拷贝构造:即创建一个新对象指针指向该资源,代表又有一个对象管理该资源,因此计数+1
		shared_ptr(Self& s) {
			_ptr = s._ptr;
			_pCount = s._pCount;
			(*_pCount)++;
		}

		// 赋值重载:
		Self& operator=(Self& s) {
			if (_ptr != s._ptr) {  // 防止自己赋值给自己:浪费
				// 先处理旧关系,再处理新关系
				Realse();  // 和之前管理的资源断开关系,其实这里就是一次析构的过程,但是不建议直接调用析构函数(可能会引发一些未知错误), 可以将析构函数的逻辑提取出来设计 Realse 函数
				// 管理新资源
				_ptr = s._ptr;
				_pCount = s._pCount;
				(*_pCount)++;
			}
			return *this;
		}

		void Realse() {
			if (--(*_pCount) == 0) {
				delete _ptr;
				delete _pCount;
				_pCount = nullptr;
				_ptr = nullptr;
			}
		}

		~shared_ptr() {
			Realse();
		}

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

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

测试代码

int main() {
	my::shared_ptr<int> mySp1(new int(2024));
	my::shared_ptr<int> mySp2 = mySp1;
	my::shared_ptr<int> mySp3;
	mySp3 = mySp1;


	cout << *mySp1 << '\n';
	cout << *mySp2 << '\n';
	cout << *mySp3 << '\n';
}



上面的代码还存在一个问题:析构函数中 delete _ptr 释放指向的空间资源,按照C++语法,
如果 _ptr = new int(),则 delete _ptr;
如果 _ptr = new int[10],则 delete[] _ptr
如果不对应使用 正确的delete,会导致内存泄漏及其他一些问题
因此析构函数中的 realse 函数就不能固定写死成:delete _ptr
可以使用仿函数解决此问题:定制删除器



4.添加 定制删除器

看C++库中,shared_ptr 的删除器(仿函数),并不是在函数模板参数处传递,而是直接作为 构造函数的参数传递,这意味着它需要多写一个 函数模板的构造函数


在这里插入图片描述

namespace my
{
	template<class T>
	class shared_ptr
	{
		typedef shared_ptr Self;
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pCount(new int(1))
		{}


		template<class D>
		shared_ptr(T* ptr, D Delete)  // 重载函数别写 T* ptr = nullptr,默认参数只能写在一个地方,否则这里报错奇奇怪怪
			: _ptr(ptr)
			, _pCount(new int(1))
			, _Delete(Delete)
		{}


		// 拷贝构造:即创建一个新对象指针指向该资源,代表又有一个对象管理该资源,因此计数+1
		shared_ptr(Self& s) {
			_ptr = s._ptr;
			_pCount = s._pCount;
			(*_pCount)++;
		}

		// 赋值重载:
		Self& operator=(Self& s) {
			if (_ptr != s._ptr) {  // 防止自己赋值给自己:浪费
				// 先处理旧关系,再处理新关系
				Realse();  // 和之前管理的资源断开关系,其实这里就是一次析构的过程,但是不建议直接调用析构函数(可能会引发一些未知错误), 可以将析构函数的逻辑提取出来设计 Realse 函数
				// 管理新资源
				_ptr = s._ptr;
				_pCount = s._pCount;
				(*_pCount)++;
			}
			return *this;
		}

		void Realse() {
			if (--(*_pCount) == 0) {
				_Delete(_ptr);  // 使用定制删除器
				// delete _ptr;
				delete _pCount;
				_pCount = nullptr;
				_ptr = nullptr;
			}
		}

		~shared_ptr() {
			Realse();
		}

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

	private:
		T* _ptr;
		int* _pCount;
		function<void(T*)> _Delete = [](const T* ptr) {delete ptr; };  // 默认删除器:对非数组类型直接 delete
	};
}



测试代码

int main() {
	// 因为 shared_ptr 内部默认的删除器使用的是 delete,下面这里需要删除int数组,因此需要 delete[],则自己传仿函数对象
	my::shared_ptr<int> mySp1(new int[10]{ 1, 2, 3 }, [](const int* ptr) {
		delete[] ptr;
		cout << "delete[] ptr; " << '\n';
		});
	// malloc 需要使用 free 释放,需要自己传定制的删除器 
	my::shared_ptr<int> mySp2((int*)malloc(40), [](int* ptr) {
		free(ptr);
		cout << "free(ptr);" << '\n';
		});
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值