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

news2024/9/19 14:22:54

在这里插入图片描述

本文主要讲解如果简单模拟实现库中的 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.coloradmin.cn/o/2107893.html

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

相关文章

信息加解密技术

一.信息加解密技术介绍 信息加解密技术是保护数据安全、防止未授权访问的重要手段。该技术主要利用数学或物理方法&#xff0c;对电子信息在传输过程和存储体中进行保护&#xff0c;确保数据的机密性、完整性和可用性。以下是对信息加解密技术的详细解析&#xff1a; 加密技术的…

三百六十行,行行用AI,AI警官助理:让文书工作不再是负担

“ 当警察在紧张的执法现场&#xff0c;AI工具能够自动转录执法记录仪中的音频&#xff0c;迅速生成警务报告&#xff0c;成为警官们的得力助手&#xff0c;极大地提高了工作效率。 ” AI警官助理&#xff1a;警务工作的革命 最近&#xff0c;一款人工智能工具在警务领域引…

C/C++ 中的算术运算及其陷阱(详解,举例分析)

在C/C编程中&#xff0c;算术运算是非常基础且常用的操作。然而&#xff0c;这些看似简单的运算背后却隐藏着一些潜在的陷阱&#xff0c;如果不加以注意&#xff0c;可能会导致程序出现难以预料的错误。本文将探讨C/C中常见的算术运算及其潜在的陷阱&#xff0c;并通过实例进行…

「Python程序设计」模块式编程:函数

​小时候&#xff0c;我们都或多或少的玩过一些积木玩具。通过把动物&#xff0c;或者是人物的各个组成部分&#xff0c;一小块&#xff0c;一小块地搭建起来&#xff0c;最终&#xff0c;就组成了我们最终想要的形状。这有点类似于乐高积木&#xff0c;通过把固定的块状物&…

Android Launcher3

一、定义与功能 Android Launcher是Android操作系统中的一个重要组件&#xff0c;它负责管理和呈现用户界面&#xff0c;包括桌面、应用程序抽屉和部件。Launcher不仅为用户提供了一个启动应用程序的入口&#xff0c;还允许用户自定义手机的主屏幕、图标、小部件布局以及一些基…

家里有宠物应该用哪款宠物空气净化器?希喂、美的真实测评

养了猫之后&#xff0c;从此我的生活开始有颜色&#xff0c;终于有声音了&#xff0c;每天下班回家终于不是直接就躺在沙发上然后洗洗就睡&#xff0c;现在有猫咪陪着我一起玩&#xff0c;甚至还会和它聊聊天&#xff0c;家里我走到哪它就跟到哪&#xff0c;身后多了一个小跟屁…

智能手机、汽车新应用,星纪魅族幸运星号”卫星即将发射

朋友们&#xff01;你想象过我们的智能手机和汽车能与卫星直接通信吗&#xff1f; 这听起来像是科幻小说里的情节&#xff0c;但很快&#xff0c;这将成为现实&#xff01;星纪魅族科技最近宣布了一个振奋人心的消息——他们将与时空道宇合作发射“星纪魅族幸运星号”卫星。这…

专业软件测试服务机构分享:小程序测试步骤和作用

在数字经济飞速发展的今天&#xff0c;小程序因其轻量、便捷的特点受到了广泛关注。作为技术服务的重要组成部分&#xff0c;软件测试成为确保小程序质量的关键环节。 一、小程序测试的定义   小程序测试是指对小程序进行系统性验证和验证的过程&#xff0c;旨在检查其功能、…

身份证实名认证-实名认证API接口文档

1、接口介绍及适用范围 身份证实名认证是指通过验证个人身份证信息的真实性&#xff0c;来确认用户身份的一种安全验证方式。这种认证方式广泛应用于各种需要身份验证的场合&#xff0c;如金融交易、社交媒体注册、网络游戏登录、电子商务平台购物等。 2、接口地址 输入姓名和…

ElasticSearch-集群读写

ES跨集群搜索&#xff08;CCS&#xff09;分片的设计和管理 算分不准 dfs_query_then_fetch如何设计分片数 ES底层读写工作原理 ES写入数据的过程ES读取数据的过程写数据底层原理 提升集群的读写性能 提升集群读取性能提升集群写入性能 ES跨集群搜索&#xff08;CCS&#xff0…

git管理历险记

本篇文章主要是记录一下公司内git管理策略的变更&#xff0c;又如何因地制宜的磨合出适合团队的方法论&#xff0c;以便未来的职业生涯遇到类似的问题可以稍微触类旁通下。 传统git策略 dev -> test -> pre -> main 这也是比较经典的一个环境对应一个分支&#xff…

[pytorch] --- pytorch基础之模型验证套路

利用已经训练好的模型&#xff0c;拿出训练集中的部分数据进行测试 下面给出完整的示例代码&#xff1a; # -*- coding: utf-8 -*- # 作者&#xff1a;小土堆 # 公众号&#xff1a;土堆碎念 import torch import torchvision from PIL import Image from torch import nnimage…

游戏行业社招上岸指南

鉴于游戏行业的蓬勃发展&#xff0c;游戏领域的各大头部企业每年仍然有大量用人需求。即将到来的“金九银十”可谓是招聘者与应聘者的双向奔赴。 然而除了校招之外&#xff0c;社招同样是秋招中不可忽视的组成部分。因此&#xff0c;这份社招求职指南也终于千呼万唤始出来&…

FPGA第 9 篇,Verilog 中的关键字和基数

前言 在 Verilog 中&#xff0c;关键字&#xff08;Keywords&#xff09;和基数&#xff08;Radix&#xff09;是语言的重要组成部分&#xff0c;它们有助于描述和定义硬件设计。上期分享了 Verilog 的基本使用&#xff0c;以及数据类型、逻辑值和算数运算符的简单应用&#x…

服务器安装pytorch-阿里云-centos7

原文阅读&#xff1a;【巨人肩膀社区专栏分享】服务器安装pytorch-阿里云-centos7 1、创建一个虚拟环境 conda create -n pytorch python3.10 安装成功&#xff1a; &#xfeff; &#xfeff;&#xfeff; 但是使用上面的命令会失败&#xff08;疑问&#xff1f;&#xf…

Apache Guacamole 安装及配置VNC远程桌面控制

文章目录 官网简介支持多种协议无插件浏览器访问配置和管理应用场景 Podman 部署 Apache Guacamole拉取 docker 镜像docker-compose.yml部署 PostgreSQL生成 initdb.sql 脚本部署 guacamole Guacamole 基本用法配置 VNC 连接 Mac 电脑开启自带的 VNC 服务 官网 https://guacam…

编程式路由跳转

点击左侧的导航栏&#xff0c;会跳转到相应的页面。可以在导航栏上定义一个点击事件&#xff0c;在vue-router中解构useRouter,把useRouter赋给router,使用***router.push()***就可以实现编程式路由跳转。 路由跳转有:to,push,replace push模式在浏览器上有历史记录&#xff…

【华为】测试工程师面试题汇总,你可知道华为的高薪技术岗有多香~

华为一直是求职者重点投递的热门企业&#xff0c;面对丰厚的薪资福利&#xff0c;无数985、211的学子挤破脑袋都想占据一席之地。 华为2021年发放工资、薪金及其他福利方面的费用达1371亿元人民币&#xff0c;按华为19.5万员工计算&#xff0c;华为员工人均年薪为70.3万&#…

[每日一题]3174. 清除数字

本专栏内容为&#xff1a;算法学习专栏&#xff0c;分为优选算法专栏&#xff0c;贪心算法专栏&#xff0c;动态规划专栏以及递归&#xff0c;搜索与回溯算法专栏四部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小…

Vue Router 入门指南:基础配置、路由守卫与动态路由

Vue Router 入门指南&#xff1a;基础配置、路由守卫与动态路由 简介&#xff1a; Vue Router 是 Vue.js 官方的路由管理工具&#xff0c;用于在 Vue 应用中实现页面导航。掌握 Vue Router 的基本配置、路由守卫以及动态路由和懒加载是构建复杂 Vue 应用的基础。本文将详细介绍…