【C++11】智能指针

news2024/12/23 9:33:39


目录

一、异常层层嵌套+执行流乱跳容易导致内存泄漏

二、使用智能指针解决上述问题

1、RAII

2、像指针一样

3、智能指针=RAII+运算符重载

三、C++98的auto_ptr

四、C++11的unique_ptr和shared_ptr

1、unique_ptr唯一指针

2、shared_ptr共享指针

2.1shared_ptr是否线程安全

2.2shared_ptr的死穴——循环引用

3、weak_ptr弱指针

五、定制删除器


一、异常层层嵌套+执行流乱跳容易导致内存泄漏

int div()
{
	throw invalid_argument("除0错误");
}
void Func()
{
	// 1、如果p1这里new 抛异常会如何?
	// 2、如果p2这里new 抛异常会如何?
	// 3、如果div调用这里又会抛异常会如何?
	int* p1 = new int;
	int* p2 = nullptr;
	try
	{
		int* p2 = new int;
		try
		{
			cout << div() << endl;
		}
		catch (...)
		{
			delete p1;
			delete p2;
			throw;
		}
	}
	catch (...)
	{
		
	}
	delete p1;
	delete p2;
}
int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

        本来只想对div()的异常进行捕获,但前边的new如果失败也会抛出异常,导致try/catch层层嵌套,加之异常自带乱跳属性,极有可能造成内存泄漏。可以使用智能指针解决该问题。

二、使用智能指针解决上述问题

1、RAII

        RAII是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。对象构造时获取资源,对象析构时自动释放资源。(可以利用局部对象出作用域自动调用析构函数完成对象资源的清理)

2、像指针一样

        为了让智能指针像指针一样支持*、->等运算符,还需要在智能指针类中去重载这些运算符。

3、智能指针=RAII+运算符重载

        可以把new出来的资源交给智能指针的对象进行管理,对象出了作用域会调用析构函数对资源进行释放。智能指针可以减少内存泄漏的风险。

template <class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}
	~SmartPtr()
	{
		if(_ptr)
			delete[] _ptr;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	T& operator[](size_t pos)
	{
		return _ptr[pos];
	}
private:
	T* _ptr;
};
int div()
{
	throw invalid_argument("除0错误");
}
void Func()
{
	SmartPtr<int> sp1(new int[10]);
	SmartPtr<int> sp2(new int[2]);
	cout << sp1[0] << endl;
	cout << div() << endl;
}
int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

三、C++98的auto_ptr

        auto_ptr的拷贝构造和赋值运算符重载会将用于拷贝/赋值的对象清空,对象悬空。很多公司会明确要求禁止使用auto_ptr。

四、C++11的unique_ptr和shared_ptr

1、unique_ptr唯一指针

        unique_ptr直接将拷贝构造和赋值给禁用了。简单粗暴。

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

2、shared_ptr共享指针

        使用构造函数构造一个shared_ptr的对象,这个对象将会获得一个指向资源的指针和一个指向堆区的计数器。每当拷贝构造或赋值时,让这个计数器++。每当减少一个指向该资源的对象,计数器--,直到计数器为零才释放资源及计数器。

namespace jly
{
	template <class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr)//构造
			:_ptr(ptr)
			, _pCount(new int(1))
		{}
		~shared_ptr()
		{
			Release();
		}
		shared_ptr(const shared_ptr& sp)//拷贝构造
			:_ptr(sp._ptr)
			, _pCount(sp._pCount)
		{
			++(*_pCount);
		}
		shared_ptr& operator= (const shared_ptr& sp)//赋值运算符重载
		{
			//需要先判断资源的地址是不是一样,有可能是相同的资源赋值有可能是不同的资源赋值
			if (_ptr != sp._ptr)//资源不一样
			{
				Release();
				_ptr = sp._ptr;
				_pCount = sp._Pcount;
				++(*_pCount);
			}
			//资源一样的话不用动
			return *this;
		}
		void Release()
		{
			if (--(*_pCount) == 0)//计数减到零,delete资源
			{
				delete _pCount;
                delete _ptr;
			}
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pCount;
	};
}

2.1shared_ptr是否线程安全

        说到计数器,就应该想到多线程场景。一旦有多个线程同时访问计数器,对其进行++--操作,势必会发生计数紊乱的问题。需要使用互斥锁对计数器加锁保护:

namespace jly
{
	template <class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr)//构造
			:_ptr(ptr)
			, _pCount(new int(1))
			,_pMutex(new mutex)
		{}
		~shared_ptr()
		{
			Release();
		}
		shared_ptr(const shared_ptr& sp)//拷贝构造
			:_ptr(sp._ptr)
			, _pCount(sp._pCount)
			,_pMutex(sp._pMutex)
		{
			_pMutex->lock();
			++(*_pCount);
			_pMutex->unlock();
		}
		shared_ptr& operator= (const shared_ptr& sp)//赋值运算符重载
		{
			//需要先判断资源的地址是不是一样,有可能是相同的资源赋值有可能是不同的资源赋值
			if (_ptr != sp._ptr)//资源不一样
			{
				Release();
				_ptr = sp._ptr;
				_pCount = sp._Pcount;
				_pMutex = sp._pMutex;
				_pMutex->lock();
				++(*_pCount);
				_pMutex->unlock();
			}
			//资源一样的话不用动

			return *this;
		}
		void Release()
		{
			bool flag = false;
			_pMutex->lock();
			if (--(*_pCount) == 0)//计数减到零,delete资源
			{
				flag = true;
				delete _ptr;
				delete _pCount;
			}
			_pMutex->unlock();
			if (flag==true)//最后一次记得释放锁
				delete _pMutex;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		int* _pCount;
		T* _ptr;
		mutex* _pMutex;
	};
}
struct Date
{
	int _year=0;
	int _month=0;
	int _day=0;
};
int main()
{
	int n = 1000000;
	jly::shared_ptr<Date> sp1(new Date);
	mutex mtx;
	thread t1([&]()
	{
		for (int i = 0; i < n; ++i)
		{
			jly::shared_ptr<Date> sp2 = sp1;
			mtx.lock();
			(sp2->_day)++;
			(sp2->_month)++;
			(sp2->_year)++;
			mtx.unlock();
		}
	});
	thread t2([&]()
	{
		for (int i = 0; i < n; ++i)
		{
			jly::shared_ptr<Date> sp3 = sp1;
			mtx.lock();
			(sp3->_day)++;
			(sp3->_month)++;
			(sp3->_year)++;
			mtx.unlock();
		}
	});
	t1.join();
	t2.join();
	std::cout << (sp1->_day) << " " << (sp1->_month) << " " << (sp1->_year) << std::endl;//cout不明确
	return 0;
}

        共享指针只对计数器来说是线程安全的,但是对于共享指针指向的资源,却是线程不安全的。如上方代码,将sp1赋值给sp2和sp3,并在lambda表达式中对date中的成员变量进行++,这时资源的是线程不安全的,多线程形式使用共享指针改动资源时需要额外申请一把互斥锁进行保护。

2.2shared_ptr的死穴——循环引用

        使用shared_ptr一旦出现循环引用的现象,将会造成内存泄漏。为了解决这一问题,可以使用weak_ptr。

3、weak_ptr弱指针

        弱指针通常和共享指针搭配使用,解决共享指针循环引用问题。

        weak_ptr支持无参的构造、支持拷贝构造、shared_ptr的拷贝构造。但是不支持使用指针进行构造,不支持RAII。

         使用weak_ptr解决shared_ptr的循环引用问题。weak_ptr本身支持shared_ptr的拷贝与赋值,且不会增加引用计数。(weak_ptr不参与资源的管理)

五、定制删除器

        析构的方式多种多样,例如数组和文件指针的释放方式不一样。这个时候就需要使用定制删除器进行对象的析构。

template <class T>
struct Delete
{
	void operator()(const T* ptr)
	{
		delete[] ptr;
	}
};
int mian()
{
	shared_ptr<int> sp1(new int[10], Delete<int>());
	shared_ptr<string> sp2(new string[10], Delete<string>());

	shared_ptr<string> sp3(new string[10], [](string* ptr) {delete[] ptr; });
	shared_ptr<FILE> sp4(fopen("test.txt", "r"), [](FILE* ptr) {fclose(ptr); });
	return 0;
}
namespace jly
{
	template<class T>
	class default_delete
	{
	public:
		void operator()(T* ptr)
		{
			delete ptr;
		}
	};
	//default_delete<T>作为默认删除器
	template<class T, class D = default_delete<T>>
	class weak_ptr
	{
	public:
	};
}

        外部有传入可调用对象就使用外部传入的,否则使用默认删除器。

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

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

相关文章

MySQL_第09章_子查询

第09章_子查询 讲师&#xff1a;尚硅谷 - 宋红康&#xff08;江湖人称&#xff1a;康师傅&#xff09; 官网&#xff1a; http://www.atguigu.com 子查询指一个查询语句嵌套在另一个查询语句内部的查询&#xff0c;这个特性从 MySQL 4.1 开始引入。 SQL 中子查询的使用大大…

计算机组成原理 作业5

作业5 题量: 21 满分: 100 作答时间:03-23 09:45至03-29 23:59 91.7分 一. 单选题&#xff08;共11题&#xff0c;35分&#xff09; 1. (单选题, 3分)计算机的存储器采用分级存储体系的主要目的是________。 A. 便于读写数据B. 便于系统…

NetSuite Sublist解释

今朝汇编一下Sublist主题的知识点以备忘。 2个数据源类型 Related Record - 以Saved Search建立的关联记录&#xff1b;Child Record - 父子表&#xff1b; 1. Related Record Saved Search关键点 这种形式的Sublist是利用Saved Search作为Sublist的数据源&#xff0c;将某…

【群智能算法】一种改进的白鲸优化算法IBWO【Matlab代码#17】

文章目录 1. 原始BWO算法1.1 勘探阶段1.2 开发阶段1.3 鲸落阶段 2. 改进白鲸优化算法2.1 Tent映射种群初始化2.2 反向学习策略 3. 部分代码展示4. 仿真结果展示5. 资源获取 1. 原始BWO算法 BWO算法的种群初始化和大多数智能算法相同&#xff0c;即随机产生搜索空间中的若干候选…

python协程实战

协程简介 协程(Coroutine)又称微线程、纤程&#xff0c;协程不是进程或线程&#xff0c;其执行过程类似于 Python 函数调用&#xff0c;Python 的 asyncio 模块实现的异步IO编程框架中&#xff0c;协程是对使用 async 关键字定义的异步函数的调用; 一个进程包含多个线程,类似…

MySQL学习笔记第三天

第04章 运算符 1.算术运算符 算术运算符主要用于数学运算&#xff0c;其可以连接运算符前后的两个数值或表达式&#xff0c;对数值或表达式进行加&#xff08;&#xff09;、减&#xff08;-&#xff09;、乘&#xff08;*&#xff09;、除&#xff08;/&#xff09;和取模&a…

13、go并发编程

目录 一、并发模型二、MPG并发模型三、Goroutine的使用1 - 协程使用2 - panic与defer 四、channel的同步与异步‘’1 - 同步与异步channel2 - 关闭channel 五、并发安全性1 - 资源竞争2 - 原子操作3 - 读写锁4 - 容器的并发安全 六、多路复用1 - 阻塞I/O2 - 非阻塞I/O3 - 多路复…

差分(一维+二维)

类似于数学中的求导和积分&#xff0c;差分可以看成前缀和的逆运算。 前缀和我们是求原数组的前缀和&#xff0c;这里是把原数组当成前缀和&#xff0c;构造一个差分数组来运算 以一维为例&#xff0c;如原数组为a[1],a[2],a[3]...a[n] 前缀和的思想是构造st[1]a[1],st[2]a[…

【C++】vector的使用

文章目录 1. 主要结构2. 构造函数与复制重载3. 迭代器4. 容量相关1.容量读取2.容量修改 5. 数据访问6. 数据修改1. 尾插尾删2.任意位置的插入删除 7.其他接口 在之前我们学习了string的使用与模拟实现&#xff0c;在参考文档中可以发现&#xff0c;vector和string以及其他的容器…

I/O 设备

CPU有两种方法访问IO设备 都是基于PMIO的&#xff0c;Port Mapped I/O 给IO总线上的寄存器编号&#xff0c;CPU向IO总线请求写入或读取数据 &#xff08;x86&#xff09;给特定的内存地址对应上目标IO设备&#xff0c;当CPU读取这段内存的时候&#xff0c;就会把访问转发给IO…

微服务 - Consul服务注册中心

概述 上篇说到构建良好的架构&#xff0c;依托于基础设施建设(自动化测试、自动化部署、服务监控&#xff0c;服务发现、配置中心等等)&#xff0c;决定成败的往往是基础设施建设&#xff0c;所以从搭建一个注册中心和配置中心开始我们新一阶段的启程。 注册中心 注册中心选型…

Cordic算法原理详解

目录 坐标旋转分析 Cordic算法原理 应用举例1&#xff1a;求sin值与cos值 应用举例2&#xff1a;求反正切值 cosθ的还原补偿 坐标旋转数字计算机CORDIC(COordinate Rotation DIgital Computer)算法&#xff0c;通过移位和加减运算&#xff0c;能递归计算常用函数值&#…

《Netty》从零开始学netty源码(四十一)之PoolChunk.runsAvail

runsAvail runsAvail用于记录long型的指针值&#xff0c;是一个LongPriorityQueue数组&#xff0c;LongPriorityQueue的结构如下&#xff1a; array数组用于存储handle的值&#xff0c;其中下标对应SizeClasses中pageIdx&#xff0c;size为array数组的大小&#xff0c;size的大…

1.13|1.14|1.15|1.6、GDB调试

1.13|1.14|1.15|1.6、GDB调试 1.13、GDB调试&#xff08;1&#xff09;&#xff0c;GDB调试&#xff08;2&#xff09;1. 什么是GDB2. 准备工作3. GDB命令—启动、推出、查看代码实际操作①用list查看代码 1.15、GDB调试&#xff08;3&#xff09;1. GDB命令—断点操作实际操作…

Redis 快速上手 Java 增删改查(包含 RedisTemplateConfig 的编写)

一&#xff1a;Redis 数据类型 先了解 redis 的五种基本数据类型。 String 字符串类型&#xff1a;name: "value1"List 列表&#xff1a;names: ["value1", "value2", "value2"]Set 集合&#xff1a;names: ["value1", &qu…

多源迁移学习网络补充知识用于具有不可见故障的机器智能诊断

**摘要&#xff1a;**当前基于深度迁移学习的故障诊断的大多数成功需要两个假设&#xff1a;1&#xff09;源机器的健康状态集合应当与目标机器的健康状态集合重叠;2&#xff09;目标机器样本的数量跨健康状态平衡。然而&#xff0c;这样的假设在工程场景中是不现实的&#xff…

【闲聊杂谈】HTTPS原理详解

HTTPS和HTTP的区别 HTTP虽然使用极为广泛, 但是却存在不小的安全缺陷, 主要是其数据的明文传送和消息完整性检测的缺乏, 而这两点恰好是网络支付, 网络交易等新兴应用中安全方面最需要关注的。 关于 HTTP的明文数据传输, 攻击者最常用的攻击手法就是网络嗅探, 试图从传输过程…

Redis高可用高性能缓存的应用系列06 - 热Key,大Key,并发竞争解决方案

概述 终于迎来了Redis系列的尾声&#xff0c;本文针对Redis常遇到的热Key&#xff0c;大Key&#xff0c;并发竞争解决方案进行介绍。 热Key 什么是热key?当一个key的访问量明显大于其他key的时候&#xff0c;他就可以被称为热key。 热Key带来的问题 热key占用大量的CPU资…

黑马在线教育数仓实战7

1. hive的相关的优化 1.1 hive的相关的函数(补充说明) if函数: 作用: 用于进行逻辑判断操作语法: if(条件, true返回信息,false返回信息) 注意: if函数支持嵌套使用 nvl函数: 作用: null值替换函数格式: nvl(T value, T default_value) COALESCE函数 作用: 非空查找函数:格式…

Windows安装GPU环境CUDA、深度学习框架Tensorflow和Pytorch

Windows安装GPU环境CUDA、深度学习框架Tensorflow和Pytorch 1、未安装CUDA使用tensorflow报错 import tensorflow as tf2022-03-06 15:14:38.869955: W tensorflow/stream_executor/platform/default/dso_loader.cc:60] Could not load dynamic library cudart64_110.dll; dl…