当前位置: 首页 > article >正文

【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';
		});
}




http://www.kler.cn/a/291843.html

相关文章:

  • unity 最小后监听键盘输入
  • 重拾设计模式--模板方法模式
  • uniApp上传文件踩坑日记
  • Anaconda3 pypi 清华大学TUNA镜像源使用帮助
  • uniapp video组件无法播放视频解决方案
  • Service Discovery in Microservices 客户端/服务端服务发现
  • 【Ubuntu20.04】配置深度学习环境
  • 机器学习周报(8.26-9.1)
  • 【C++ Primer Plus习题】9.3
  • C# 删除Word文档中的段落
  • Golang 内存泄漏详解:原因、检测与修复
  • 【论文阅读】LLM4CP: Adapting Large Language Models for Channel Prediction(2024)
  • 啄木鸟上门安装维修系统源码开发
  • 【个人笔记】VCS工具与命令
  • 钢铁百科:Q420DR力学性能、Q420DR执行标准、Q420DR低温容器钢板
  • 自己设计的QT系统,留个档
  • Docker 容器编排之 Docker Compose
  • Arcgis将图层转shape文件
  • 【大数据】DataX深度解析:数据同步的神器是如何工作的?
  • Android Gradle 插件的说明
  • AI生成图片,ChatGPT生成路虎女逆行打人插图。
  • 语音测试(一)ffmpeg视频转音频
  • 【2024高教社杯全国大学生数学建模竞赛】B题 生产过程中的决策问题——解题思路 代码 论文
  • 浅谈C# 虚函数
  • halcon+c#+abb机器人=激光熔覆视觉工作站
  • ActiViz中的粒子系统详细解析