【C++智能指针】智能指针的发展和循环引用的原理和解决

news2024/11/15 15:38:44

目录

1.RAIl(智能指针的雏形)

2.拷贝导致的问题以及智能指针发展历史

2.1拷贝的问题(资源被析构两次)

2.2auto_ptr(资源权转移,不建议使用)

         2.3unique_ptr(防拷贝,在不需要拷贝的情况下使用) 

         2.4shared_ptr

2.4.2循环引用的问题(重点)

 3.定制删除器


智能指针是拿来处理异常安全问题的

  • 下面这段代码如果抛异常了,那么p1就指向的空间就没有被释放,内存泄漏 
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");

	return a / b;
}

void func()
{
	int* p1 = new int;
	cout << div() << endl; // 异常安全的问题

	delete p1;
}

int main()
{
	try
	{
		func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

1.RAIl(智能指针的雏形)

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源,对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。(就是把原本资源托管给一个类对象)

优势:

  1. 不需要显式地释放资源。
  2. 采用这种方式,对象所需的资源在其生命期内始终保持有效(声明周期在调用栈帧里面,声明周期结束自动调析构函数)
template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}
    //生命周期结束,自动析构
	~SmartPtr()
	{
		cout << "delete:" << _ptr << endl;
		delete _ptr;
	}

	// 像迭代器一样使用
	T& operator*()
	{
		return *_ptr;
	}
	
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");

	return a / b;
}

void func()
{
	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2(new int);
	SmartPtr<int> sp3(new int);

	*sp1 = 10;
	cout << *sp1 << endl;
	(*sp1)++;
	(*sp1)++;
	cout << *sp1 << endl;

	cout << div() << endl;
}

int main()
{
	try
	{
		func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

 执行结果·:

解决了如果抛异常,在堆上开的空间得不到释放,导致的内存问题;

重载operator*和opertaor->,具有像指针一样的行为,可以正常访问

2.拷贝导致的问题以及智能指针发展历史

2.1拷贝的问题(资源被析构两次)

// RAII,把资源托管给一个类对象
// 用起来像指针一样
template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}

	~SmartPtr()
	{
		cout << "delete:" << _ptr << endl;
		delete _ptr;
	}

	
	T& operator*()
	{
		return *_ptr;
	}
	// 像指针一样使用
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};

int main()
{
	//SmartPtr<int> sp1(new int);
	//SmartPtr<int> sp2(sp1);

	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2(new int);
	sp2 = sp1;

	return 0;
}

执行结果

  •  默认生成的拷贝构造,赋值重载都是浅拷贝,同一块空间被析构两次,程序奔溃

不能深拷贝的原因 

 2.2auto_ptr(资源权转移,不建议使用)

//auto_ptr
namespace LDJ
{
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}

		auto_ptr(auto_ptr<T>& sp)
			:_ptr(sp._ptr)
			//:_ptr(nullptr)
		{
			// 管理权转移
			sp._ptr = nullptr;
			//swap(_ptr, sp._ptr);
		}

		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}

		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}

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

// 结论:auto_ptr是一个失败设计
int main()
{
	LDJ::auto_ptr<int> sp1(new int);
	LDJ::auto_ptr<int> sp2(sp1); // 管理权转移
	// sp1悬空
	*sp2 = 10;
	cout << *sp1 << endl;
	cout << *sp2 << endl;

	return 0;
}

执行结果

  • sp1悬空了; 

2.3unique_ptr(防拷贝,在不需要拷贝的情况下使用) 

  • unique_ptr的实现原理:简单粗暴的防拷贝
namespace LDJ
{
	template<class T, class D = default_delete<T>>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;

			}
		}

		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}

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

		unique_ptr(const unique_ptr<T>& sp) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;

	private:
		T* _ptr;
	};
}

2.4shared_ptr

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减 一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了
namespace LDJ
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pRefCount(new int(1))
		{}
		void AddRef()
		{
			 ++*(_pRefCount);
		}
		void Release()
		{

			if (--(*_pRefCount) == 0 && _ptr)
			{
				delete _ptr;
				delete _pRefCount;

			}
		}
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pRefCount(sp._pRefCount)
			, _pmtx(sp._pmtx)
		{
			AddRef();
		}

		~shared_ptr()
		{
			Release();
		}

		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pRefCount;
	}
}

2.4.1operator=需要处理一下

shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if (this != &sp)
			if (this->_pRefCount != sp._pRefCount)
			{
				Release();
				_ptr = sp._ptr;
				_pRefCount = sp._pRefCount;
				AddRef();
				return *this;
			}
		}

2.4.2循环引用的问题(重点)

struct Node
{
	~Node()
	{
		cout << "~Node" << endl;
	}
	int val;
	std::shared_ptr<Node> _next;
	std::shared_ptr<Node> _prev;
};
int main()
{
	std::shared_ptr<Node> sp1(new Node);
	std::shared_ptr<Node> sp2(new Node);
	sp1->_next = sp2;
	sp2->_prev = sp1;

	return 0;
}

执行结果:如果被析构了会打印两行~Node,然而并没有,说明sp1和sp2都没有被释放

 

循环引用的具体原因: 

解决循环引用weak_ptr

  • weak_ptr不是常规的智能指针,不增加计数支持访问,所以也不支持RAII 

struct Node
{
	~Node()
	{
		cout << "~Node" << endl;
	}
	int val;
	std::weak_ptr<Node> _next;
	std::weak_ptr<Node> _prev;
};
int main()
{
	std::shared_ptr<Node> sp1(new Node);
	std::shared_ptr<Node> sp2(new Node);
	cout << "sp1Count:" << sp1.use_count() << endl;
	cout << "sp2Count:" << sp2.use_count() << endl;
	sp1->_next = sp2;
	sp2->_prev = sp1;
	cout << "sp1Count:" << sp1.use_count() << endl;
	cout << "sp2Count:" << sp2.use_count() << endl;
	return 0;
}

 执行结果:前后sp1和sp2的计数都为1;所以可以证明weak_ptr不增加引用计数且解决了循环引用的问题;

 3.定制删除器

那么如果资源不是new出来的呢?比如:new[]、malloc、fopen,delete就不够用了

template<class T>
struct DeleteArray
{
	void operator()(const T* ptr)
	{
		delete[] ptr;
	}
};
struct DeleteFile
{
	void operator()(FILE* ptr)
	{
		fclose(ptr);
	}
};
    unique_ptr<A> up1(new A);
    unique_ptr<A, DeleteArray<A>> up2(new A[10]);
    unique_ptr<FILE, DeleteFile> up3(fopen("test.txt", "w"));

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

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

相关文章

【vue】vue-count-to(数字滚动)插件:

文章目录一、效果&#xff1a;二、使用&#xff1a;【1】安装插件【2】案例三、Options四、Functions一、效果&#xff1a; 二、使用&#xff1a; 官方文档&#xff1a; github地址——https://github.com/PanJiaChen/vue-countTo npm地址——https://www.npmjs.com/package/…

在本地PC运行 Stable Diffusion 2.0

Stable Diffusion 2.0在前几天已经发布了&#xff0c;新版本在上一个版本的基础上进行了许多改进。OpenCLIP中新的深度检测和更好的文本到图像模型是主要的改进之一。 有很多的文章介绍了Stable Diffusion 2.0的改进&#xff0c;所以我们就不多介绍了&#xff0c;这里我们将介…

30分钟部署一个kubernetes集群【1.17】

作者:李振良 kubeadm是官方社区推出的一个用于快速部署kubernetes集群的工具。 这个工具能通过两条指令完成一个kubernetes集群的部署: # 创建一个 Master 节点 $ kubeadm init# 将一个 Node 节点加入到当前集群中 $ kubeadm join <Master节点的IP和端口 >1. 安装要求…

.m3u8.sqlite文件转mp4,m3u8.sqlite文件转视频工具(开源免费)

文章目录一、预先准备1. 前提2. 主要思路3. 准备工具二、视频转换实战2.1. 软件下载2.2. TS转MP4工具2.3. 操作流程一、预先准备 1. 前提 如果已经买了课程&#xff0c;是可以下载的&#xff0c;并且腾讯课堂APP里就有下载功能。 2. 主要思路 在APP上下载视频缓存到手机本地…

postgres源码解析41 btree索引文件的创建--1

上述小节讲解了索引文件的物理创建&#xff0c;本篇讲解btree索引元组填充至索引文件的操作。先从数据结构入手&#xff0c;后深入执行流程。 postgres源码解析37 表创建执行全流程梳理–1 postgres源码解析37 表创建执行全流程梳理–2 postgres源码解析37 表创建执行全流程梳理…

详解设计模式:责任链模式

责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;也被称为职责链模式&#xff0c;是在 GoF 23 种设计模式中定义了的行为型模式。 责任链模式 是将链中的每一个节点看作是一个对象&#xff0c;每个节点处理的请求不同&#xff0c;且内部自动维护一个下一节点…

降温了好冷,总结下11月

1、这两天广东气温骤降&#xff0c;出门必须加外套了&#xff0c;emmm&#xff0c;不想出门&#xff0c;各位宝宝注意保暖吖&#xff0c;别感冒了。2、这边疫情开始放开了&#xff0c;备好感冒药&#xff0c;锻炼身体&#xff0c;做好预防&#xff0c;靠自己了。3、11月过的真快…

jsp人事考勤管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 人事考勤管理系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统采用serlvetdaobean mvc 模式&#xff0c;系统主要采用B/S模式 开发。开发环境为TOMCAT7.0,Myeclipse8.…

[附源码]计算机毕业设计基于Springboot的花店售卖系统的设计与实现

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【45-线程的实现方式-线程池的创建方式-线程池的执行顺序-CompletableFutrue异步处理】

一.知识回顾 【0.三高商城系统的专题专栏都帮你整理好了&#xff0c;请点击这里&#xff01;】 【1-系统架构演进过程】 【2-微服务系统架构需求】 【3-高性能、高并发、高可用的三高商城系统项目介绍】 【4-Linux云服务器上安装Docker】 【5-Docker安装部署MySQL和Redis服务】…

物联网毕设 -- 基于STM32的心率检测

目录 前言 1 演示视频 一 连线图 1. 原理图 2.功能概括 二 底层代码使用方式 1. 下载程序 2. 查看云平台数据 三 APP使用方式 1. 下载APP 2. 配对蓝牙 四 网盘链接 前言 分享一个之前做到心率监测项目&#xff0c;有APP端有OLED显示端&#xff0c;我会把把他放…

Windows搭建web站点:免费内网穿透发布至公网 1-2

什么是cpolar&#xff1f; cpolar是一个非常强大的内网穿透工具&#xff0c;开发调试的必备利器。 它可以将本地内网服务器的HTTP、HTTPS、TCP协议端口映射为公网地址端口&#xff0c;使得公网用户可以轻松访问您的内网服务器&#xff0c;无需部署至公网服务器。支持永久免费使…

[附源码]Python计算机毕业设计Django软考刷题小程序

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

ssm项目整合,简单的用户管理系统

ssm项目整合&#xff0c;简单的用户管理系统项目概述项目搭建创建项目1.创建maven项目2.导入项目依赖及maven资源过滤设置3.添加web支持4.配置tomcat5.配置web发布依赖jar包6.数据库的表创建7.实体类创建7.1 lombok常用注解:dao层1.daoMapper接口创建2.Mapper.xml配置文件3.myb…

物联网iot全称

物联网iot的发展 麻省理工学院Ashton教授1999年研究RFID时最早提出物联网(Internet of Things&#xff0c; IoT)这个概念。Sun公司在2003年发表文章介绍物联网的基本工作流程&#xff0c;并提出解决方案。 IoT的意思是物联网&#xff0c;全称为Internet of Things。是指通过射…

狂神说Spring学习笔记

一、Spring 1.1 简介 Spring&#xff1a;春天------>给软件行业带来了春天&#xff01;2002&#xff0c;首次推出了Spring框架的雏形&#xff1a;interface21框架&#xff01;Spring框架即以interface21框架为基础&#xff0c;经过重新设计&#xff0c;并不断丰富其内涵&a…

metaRTC新增SRS的WebRTC over TCP和turn的TCP支持

概述 在很多网络条件下&#xff0c;防火墙会阻止UDP传输&#xff0c;因此webRTC TCP的支持就很重要&#xff0c;metaRTC提供了SFU和P2P的支持。 SFU支持SRS的webRTC over TCP&#xff0c;P2P支持turn的TCP。 源代码下载 GitHub - metartc/metaRTC: A cross-platform WebRTC…

[附源码]计算机毕业设计基于SpringBoot的小说阅读系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

8.softmax回归

1. 关于回归和多类分类 分类问题从回归的单输出变成了多输出&#xff0c;这个多输出的个数类别的个数 置信度&#xff1a;置信度一词来自统计学&#xff0c;而统计学的本质是&#xff0c;用抽样的数据去估计整体的真实分布。例如&#xff0c;样本均值估计整体均值&#xff1b;还…

基于WEB的在线选课系统(asp.net)

目  录 摘要 I Abstract II 第1章 绪论 1 1.1 课题背景 1 1.2 目的和意义 1 1.3 系统设计思想 2 第2章 可行性分析 3 2.1 管理可行性 3 2.2 经济可行性 4 2.2.1 系统初期投资 4 2.2.2 系统货币的时间价值 4 2.2.3 投资回收期 5 2.2.4 纯收入 5 2.3 技术可行性 5 2…