C++:智能指针(auto_ptr/unique_ptr/shared_ptr/weak_ptr)

news2024/10/2 22:21:07

为什么需要智能指针?

C++没有垃圾回收机制。

#include<iostream>
using namespace std;

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

	return a / b;
}

void Func()
{
	// 1、如果p1这里new 抛异常会如何?
	// 2、如果p2这里new 抛异常会如何?
	// 3、如果div调用这里又会抛异常会如何?
	int* p1 = new int[10];
	int* p2 = nullptr;
	int* p3 = nullptr;

	try
	{
		p2 = new int[20];
	}
	catch (...)
	{
		delete[] p1;
		throw;
	}

	try
	{
		p3 = new int[10];
	}
	catch (...)
	{
		delete[] p1;
		delete[] p2;
		throw;
	}

	try
	{
		cout << div() << endl;
	}
	catch (...)
	{
		delete[] p1;
		delete[] p2;
		delete[] p3;

		throw;
	}

	delete[] p1;
	delete[] p2;
}

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

	return 0;
}

在多次new时有可能抛异常,就需要一层一层捕获异常并释放资源还需要条件判断,非常麻烦,否则抛异常之后delete多了或者少了都不行。一不小心就内存泄露了。

如何避免内存泄漏

1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps: 这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智 能指针来管理才有保证。

2. 采用RAII思想或者智能指针来管理资源。

3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。

4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

智能指针的使用及原理

智能指针指针包含两个部分:RAII(通过构造和析构管理资源)和像指针一样的操作符重载。

智能指针的本质时将资源生命周期与对象绑定

下面来见一见RAII的智能指针:

template<class T>
class smart_ptr
{
public:
	smart_ptr(T* ptr)
	:_ptr(ptr)
	{}

	~smart_ptr()
	{
		delete _ptr;
	}

private:
	T* _ptr;
};

我们再来实现像指针一样的部分:

template<class T>
	class smart_ptr
	{
	public:
		//RAII
		smart_ptr(T* ptr)
			:_ptr(ptr)
		{}

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


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

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}

	private:
		T* _ptr;
	};
int main()
{
	szg::smart_ptr<Date> sp1(new Date());

	cout << sp1->_year << ":" << sp1->_month << ":" << sp1->_day << endl;
	++sp1[0]._year;
	++(*sp1)._month;
	++sp1->_day;
	cout << sp1->_year << ":" << sp1->_month << ":" << sp1->_day << endl;

	return 0;
}

//0:0 : 0
//1 : 1 : 1
//delete00732778

问题一:拷贝构造会重复析构

智能指针的方式也会产生一些问题,比如拷贝构造和赋值会重复析构。

int main()
{
	szg::smart_ptr<Date> sp1(new Date());
	szg::smart_ptr<Date> sp2(sp1);
	

	return 0;
}
//delete00CE27C0
//delete00CE27C0
//直接报错

在学习解决这个问题前先了解一下智能指针的发展历史:

// C++智能指针发展历史

// C++98 auto_ptr 资源管理权转移-->对象悬空 很多公司明确要求不能使用它
// boost scoped_ptr 防拷贝
// boost shared_ptr/weak_ptr // 引用计数

// C++11 unique_ptr 防拷贝
// C++11 shared_ptr/weak_ptr // 引用计数

auto_ptr

最先提出的是auto_ptr,它是通过资源管理权转移解决重复析构问题的:

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

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

		auto_ptr(auto_ptr<T>& ap)
			:_ptr(nullptr)
		{
			std::swap(_ptr, ap._ptr);
		}

		auto_ptr<T>& operator=(auto_ptr<T>& ap) = delete;

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

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}

	private:
		T* _ptr;
	};

貌似解决了问题,但会导致对象悬空,不建议使用。

unique_ptr

unique_ptr脱胎于boost库(C++标准库的预备库)的scoped_ptr,通过禁用拷贝的方式解决重复析构问题。

	template<class T>
	class unique_ptr
	{
	public:
		//RAII
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

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

		unique_ptr(unique_ptr<T>& ap) = delete;
		unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;

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

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}

	private:
		T* _ptr;
	};

shared_ptr/weak_ptr

shared_ptr/weak_ptr使用引用计数方式防止拷贝构造。

新开辟一块区域作为引用计数的空间:

template<class T>
	class shared_ptr
	{
	public:
		//RAII
		shared_ptr(T* ptr)
			:_ptr(ptr)
			, _pcnt(new int(1))
			,_pmutex(new std::mutex)
		{}

		void release()
		{
			bool flag = false;
			_pmutex->lock();
			if (--(*_pcnt) == 0 && _ptr)
			{
				delete _ptr;
				delete _pcnt;
				flag = true;
			}
			_pmutex->unlock();
			if (flag)
			{
				delete _pmutex;
			}
		}

		~shared_ptr()
		{
			release();
		}

		shared_ptr(const shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		,_pcnt(sp._pcnt)
		,_pmutex(sp._pmutex)
		{
			_pmutex->lock();
			++(*_pcnt);
			_pmutex->unlock();
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				release();
				_ptr = sp._ptr;
				_pcnt = sp._pcnt;
				_pmutex = sp._pmutex;
				_pmutex->lock();
				++(*_pcnt);
				_pmutex->unlock();
			}
			return *this;
		}



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

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}

	private:
		T* _ptr;
		int* _pcnt;
		std::mutex* _pmutex;
	};

int main()
{
	szg::shared_ptr<int> sp1(new int(0));
	szg::shared_ptr<int> sp2(sp1);
	szg::shared_ptr<int> sp3(sp2);
	(*sp1)++;
	(*sp2)++;

	cout << *sp1 << endl;
	cout << *sp2 << endl;
	sp1 = sp2;

	szg::shared_ptr<int> sp4(new int(10));
	szg::shared_ptr<int> sp5(sp4);
	sp1 = sp4;

	

	return 0;
}

std::shared_ptr的循环引用

循环引用分析:

1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动 delete。

2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。

3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上 一个节点。

4. 也就是说_next析构了,node2就释放了。

5. 也就是说_prev析构了,node1就释放了。

6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev 属于node2成员,所以这就叫循环引用,谁也不会释放。

 

struct ListNode
{
	ListNode()
	{
		val = 0;
	}
	int val;
	szg::shared_ptr<ListNode> _next;
	szg::shared_ptr<ListNode> _prev;


	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

void test_shared_ptr2()
{
	szg::shared_ptr<ListNode> n1(new ListNode);
	szg::shared_ptr<ListNode> n2(new ListNode);

	n1->_next = n2;
	n2->_prev = n1;

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;
}

int main()
{
	test_shared_ptr2();
	return 0;
}

循环引用的本质是节点内含智能指针,且节点内的智能指针互相引用(指向对方)。

由于计数引用导致了:

因为你中有我,我不能先释放

因为我中有你,你不能先释放

导致都不能释放,直接造成内存泄漏

循环引用是shared_ptr的死穴,要用weak_ptr解决。

weak_ptr

weak_ptr解决循环引用是通过使weak_ptr可以指向资源,但是不增加引用计数实现的。不支持管理资源,只用于share_ptr的拷贝,是shared_ptr的小弟。

	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}

		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}

		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();
			return *this;
		}

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

		T* operator->()
		{
			return _ptr;
		}
	public:
		T* _ptr;
	};
struct ListNode
{
	ListNode()
	{
		val = 0;
	}
	int val;
	// 可以指向资源/访问资源,不参与资源管理,不增加引用计数
	szg::weak_ptr<ListNode> _next;
	szg::weak_ptr<ListNode> _prev;

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

void test_shared_ptr2()
{
	szg::shared_ptr<ListNode> n1(new ListNode);
	szg::shared_ptr<ListNode> n2(new ListNode);

	n1->_next = n2;
	n2->_prev = n1;

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;
}

int main()
{
	test_shared_ptr2();
	return 0;
}

定制删除器

定制一个资源释放的方式,毕竟newnew[]出来的资源的释放方式是不一样的,得匹配deletedelete[]

只要使用包装器对象保存释放资源的方法即可:

template<class T>
	class shared_ptr
	{
	public:
		//RAII
		shared_ptr(T* ptr = nullptr, const std::function<void(T*)> func = [](T* t) {delete t;})
			:_ptr(ptr)
			, _pcnt(new int(1))
			,_pmutex(new std::mutex)
		{
			_func = func;
		}

		void release()
		{
			bool flag = false;
			_pmutex->lock();
			if (--(*_pcnt) == 0 && _ptr)
			{
				_func(_ptr);
				delete _pcnt;
				flag = true;
			}
			_pmutex->unlock();
			if (flag)
			{
				std::cout << "delete" << std::endl;
				delete _pmutex;
			}
		}

		~shared_ptr()
		{
			release();
		}

		shared_ptr(const shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		,_pcnt(sp._pcnt)
		,_pmutex(sp._pmutex)
		{
			_func = sp._func;
			_pmutex->lock();
			++(*_pcnt);
			_pmutex->unlock();
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				release();
				_ptr = sp._ptr;
				_pcnt = sp._pcnt;
				_pmutex = sp._pmutex;
				_func = sp._func;
				_pmutex->lock();
				++(*_pcnt);
				_pmutex->unlock();
			}
			return *this;
		}

		int use_count()
		{
			_pmutex->lock();
			int ret = (*_pcnt);
			_pmutex->unlock();
			return ret;
		}

		T* get() const
		{
			return _ptr;
		}



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

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}

	private:
		T* _ptr;
		int* _pcnt;
		std::mutex* _pmutex;
		std::function<void(T*)> _func;
	};
int main()
{
	szg::shared_ptr<string> sp1(new string[10], [](string* s) {delete[] s;});
	szg::shared_ptr<string> sp2(sp1);
	szg::shared_ptr<string> sp3(sp2);
	(*sp1) = "111111";
	(*sp2) = "222222";

	cout << *sp1 << endl;
	cout << *sp2 << endl;
	sp1 = sp2;

	szg::shared_ptr<string> sp4(new string[10], [](string* s) {delete[] s;});
	szg::shared_ptr<string> sp5(sp4);
	sp1 = sp4;

	

	return 0;
}

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

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

相关文章

网络原理数据链路层

嘿嘿,又见面了,今天为大家带来数据链路层的相关知识.这个层面的知识离咱们程序员太遥远了,我们简单介绍一下就行 1.以太网 2.认识Mac地址 3.区分Mac地址和IP地址 4.MTU 5.DNS 1.以太网 以太网是数据链路层和物理层的使用的网络,物理层用的不咋多,我们就先不讲了,直接看数…

论文阅读 - Segment Anything

文章目录 0 前言1 预备知识1.1 深度学习训练框架1.2 语义分割训练框架 2 SAM的任务3 SAM的模型3.1 模型整体结构3.2 Image encoder3.3 Prompt encoder3.4 Mask decoder3.5 训练细节 4 SAM的数据4.1 模型辅助的手动标注阶段4.2 半自动阶段4.3 全自动阶段 5 SAM的应用5.1 拿来主义…

什么是感知机——图文并茂,由浅入深

什么是感知机——图文并茂&#xff0c;由浅入深 文章目录 什么是感知机——图文并茂&#xff0c;由浅入深引言感知机的引入宝宝版青年版老夫聊发少年狂版激活函数 感知机的应用与门或门 感知机与深度学习感知机与神经网络感知机和深度学习什么关系呢&#xff1f; 引言 生活中常…

【4月比赛合集】19场可报名的「创新应用」和「程序设计」大奖赛,任君挑选!

CompHub 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号同时会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 更多比赛信息见 CompHub主页 或 点击文末阅读原文 以下信息仅供参考&#xff0c;以比赛官网为准 目录 创新应用赛&…

【SpringBoot】一:SpringBoot的基础(上)

文章目录 1. 脚手架创建项目1.1使用Spring Initializr1.2 IDEA中使用脚手架创建项目 2. 代码结构2.1 单一结构2.2 多模块2.3 包和主类2.4 pom文件2.4.1 父项目2.4.2 启动器2.4.3 不使用父项目 3. 运行SpringBoot项目 1. 脚手架创建项目 脚手架辅助创建程序的工具&#xff0c;S…

《Java8实战》第12章 新的日期和时间 API

原来的Java的时间类Date、java.util.Calendar类都不太好&#xff0c;以语言无关方式格式化和解析日期或时间的 DateFormat 方法也有线程安全的问题 12.1 LocalDate、LocalTime、LocalDateTime、Instant、Duration 以及 Period 12.1.1 使用 LocalDate 和 LocalTime LocalDate…

Maven的概述

Maven是干什么用的 maven提供了一套标准的项目结构&#xff0c;这样可以让不同编译器所写的代码在任何一个编译器上都可以运行。 maven提供了一套标准化的构建流程 编译&#xff0c;测试&#xff0c;打包&#xff0c;发布->maven提供了简单的命令可以完成这些操作&#xf…

1秒解决notion客户端所有问题-历史上最简单

1 前言 你是否安装了enhancer后&#xff0c;notion打不开&#xff0c;一直报错&#xff1f;你是否为实现notion客户端汉化和大纲的各种操作而各种苦恼&#xff1f;你是否不习惯使用网页的开始&#xff0c;很想有一个客户端的notion&#xff01; 全部解决&#xff01; 2 网页…

如何理解线程池

线程池的核心状态 核心状态说明 在线程池的核心类ThreadPoolExecutor中&#xff0c;定义了几个线程池在运行过程中的核心状态&#xff0c;源码如下&#xff1a; private static final int COUNT_BITS Integer.SIZE - 3;private static final int CAPACITY (1 << CO…

不良事件报告系统源码,PHP医院安全(不良)事件报告系统源码,在大型医院稳定运行多年

PHP医院安全&#xff08;不良&#xff09;事件报告系统源码&#xff0c;不良事件系统源码&#xff0c;有演示&#xff0c;在大型医院稳定运行多年。 系统技术说明 技术架构&#xff1a;前后端分离&#xff0c;仓储模式 开发语言&#xff1a;PHP 开发工具&#xff1a;VSco…

AE开发20210531之色彩设置、渐变色、符号颜色、属性框内数据操作、另存图层、设计添加属性对话框

笔记 选择ID变化后&#xff0c;清空symbol&#xff0c;添加进新的来&#xff0c;渐变色设置符号颜色对属性框中数据进行操作另存图层&#xff0c;save方法savelayer打开属性对话框自己设计添加属性对话框 课程设计下一节课&#xff0c;图层的渲染 点符号&#xff0c;线符号&…

satoken+ gateway网关统一鉴权 初版

一&#xff1a;感谢大佬 本博客内容 参考了satoken官网实现&#xff0c;satoken官网地址&#xff1a; https://sa-token.cc/doc.html#/micro/gateway-auth 二&#xff1a;项目层级介绍 jinyi-gateway 网关服务jinyi-user-service 用户服务 2.1 jinyi-user-api 2.2 jinyi-use…

Docker 快速上手

目录 一、初始Docker 二、Docker基本操作 1、镜像操作命令 2、容器相关命令 3、数据卷 三、Deckerfile自定义镜像 1、镜像结构 2、自定义镜像 四、DockerCompose 一、初始Docker 镜像(lmage):Docker将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起&am…

Docker中配置Mysql主从复制

新建主服务器容器实例3307 进入/mydata/mysql-master/conf目录下新建my.cnf vim my.cnf [mysqld] ## 设置server_id&#xff0c;同一局域网中需要唯一 server_id101 ## 指定不需要同步的数据库名称 binlog-ignore-dbmysql ## 开启二进制日志功能 log-binmall-mysql-bin …

Spring中读取和存储Bean对象(5000字详解)

在Spring项目的创建和使用(Spring项目的创建和使用_蜡笔小心眼子&#xff01;的博客-CSDN博客)中&#xff0c;我们详细讲解了如何通过配置XML文件存取Bean对象&#xff0c;但是没新增一个Bean对象时就需要添加一个Bean标签&#xff0c;而且如果同一个Bean对象被多次注册到Sprin…

轻松掌握K8S命名空间、Pod、Deployment部署工具

1、NameSpace命名空间 在官方可视化工具界面为如下 也可以使用kubectl命令行具体看使用命令行操作 2、Pod应用组 k8s所说的应用通常就是指pod&#xff0c;一个pod可以部署多个容器。容器间共享网络空间&#xff0c;共享存储 3、根据应用类型选择部署Pod的工具 k8s官方也有对…

运行Omniverse Replicator Headlessly

运行Replicator Headlessly 学习目标 本教程的目的是向您展示如何不费吹灰之力地使用 Replicator。 Headless 在这种情况下意味着计算设备没有显示器或外围设备&#xff0c;例如键盘和鼠标。 为了做到这一点&#xff0c;我们将对 Replicator 的核心功能 - “Hello World”中解…

MyBatis-Plus Generator v2.0.8 ~ v3.1.1 最新代码自动生成器

一、概述 官网&#xff1a;https://baomidou.com/ 官方文档 &#xff1a;https://baomidou.com/pages/56bac0/ 官方源码地址&#xff1a; https://gitee.com/baomidou/mybatis-plus 官方原话&#xff1a; AutoGenerator 是 MyBatis-Plus 的代码生成器&#xff0c;通过 Auto…

高级篇七、InnoDB数据存储结构

1、数据库的存储结构&#xff1a; 页 1.1 磁盘与内存交互的基本单位&#xff1a;页 1.2 页结构概述 页a&#xff0c;页b&#xff0c;页c … 页n 这些页可以不在物理结构上相连&#xff0c;只要通过双向链表相关联即可每个数据页中的记录会按照主键值从小到大的顺序组成一个单项…

Qt5.12实战之图形编程初识

演示效果: 1.绘制条件: 1. 绘图设备-> QPainter 2.画笔->QPen --->字体 (QFont) 3.画刷->QBrush-->自己定义画刷(QPixmap) 4.绘制事件->QPaintEvent 绘图步骤: 1.重写基类的虚函数 void paintEvent(QPaintEvent *event); 2.在虚函数 void paintEvent…