【C++11】智能指针


目录

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

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

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:
	};
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

不得不说的创建型模式-工厂方法模式

工厂方法模式是创建型模式之一&#xff0c;它定义了一个用于创建对象的接口&#xff0c;但将具体创建的过程延迟到子类中进行。换句话说&#xff0c;它提供了一种通过调用工厂方法来实例化对象的方法&#xff0c;而不是通过直接使用 new 关键字来实例化对象。 下面是一个使用 C…

浅理解JavaScript数组去重的方法(划重点),当面试官问如何实现数组去重时,你可以这样做...

文章目录 &#x1f4cb;前言&#x1f3af;什么是数组去重&#xff0c;运用场景是什么&#xff1f;&#x1f3af;常用的数组去重方法&#x1f9e9;使用 Set 对象&#x1f9e9;使用 Object&#xff08;对象、基于Hash哈希表&#xff09; 或 Map&#x1f9e9;使用 filter 方法与 i…

Smartbi电子表格软件架构与差异化特色

Smartbi电子表格软件选择与Excel结合&#xff0c;原因在于Excel一直被模仿&#xff0c;从未被超越。虽然市场上的报表软件很多&#xff0c;但存在太多的不完美。国外的产品功能复杂、难于学习&#xff08;控件方式&#xff09;&#xff0c;做不了中国式复杂格式的报表&#xff…

Python双向循环链表的操作

目录 一、双向循环链表 双向循环链表图 二、双向循环链表的操作 1、判断链表是否为空 2&#xff0c;链表长度 3&#xff0c;遍历整个链表 4&#xff0c;在链表头部添加元素 5、链表尾部添加元素 6&#xff0c;在指定位置插入元素 7&#xff0c;修改指定位置的元素 8&a…

VS Code 插件开发概览

VS Code 插件开发概览 前言 VS Code作为开发者的代码开发利器&#xff0c;越来越受开发者的喜爱。像我身边的前端&#xff0c;每天80%的开发工作都是在VS Code上完成的。随着人们对它的使用&#xff0c;不再满足简单的优雅&#xff0c;舒服写代码这一基本需求。有些人利用它进…

阿里ARouter 路由框架解析

一、简介 众所周知&#xff0c;在日常开发中&#xff0c;随着项目业务越来越复杂&#xff0c;项目中的代码量也越来越多&#xff0c;如果维护、扩展、解耦等成了一个非常头疼问题&#xff0c;随之孕育而生的诸如插件化、组件化、模块化等热门技术。 而其中组件化中一项的难点&…

深入理解Linux多线程

致前行的人&#xff1a; 昨日渐多&#xff0c;明日愈少&#xff0c;今日还在&#xff0c;不要为成功而努力&#xff0c;要为做一个有价值的人而努力。人生道路上充满了坎坷&#xff0c;谁也不可能一帆风顺。只有在最困难的时刻&#xff0c;才能体会到无助的含义。 目录 1.理解…

SpringBoot集成MyBatis-yml自动化配置原理详解

SpringBoot集成MyBatis-yml自动化配置原理详解 简介&#xff1a;spring boot整合mybatis开发web系统目前来说是市面上主流的框架&#xff0c;每个Java程序和springboot mybatis相处的时间可谓是比和自己女朋友相处的时间都多&#xff0c;但是springboot mybatis并没有得到你的真…

适用于 Windows 的 5 个最好的 PDF 转换器应用程序

由于稳定性、高分辨率、高安全性、易于传输等特点&#xff0c;PDF已经成为我们日常工作中最常用的格式。我们在享受PDF带来便利的同时&#xff0c;也发现PDF带来了一些不便&#xff0c;其中最大的问题就是PDF内容的编辑难度。同时&#xff0c;并不是所有的文件都是PDF格式的&am…

代码优化- 前端优化

常量折叠 基本思想&#xff1a;在编译期间计算表达式的值&#xff08;编译时静态计算&#xff09; 例如&#xff1a;a 3 5 > a 8&#xff0c;if (true && false) ... > if (false) 好处是&#xff1a;语法树的节点数量减少了&#xff0c;意味着编译器要维护…

Ubuntu上跑通PaddleOCR

书接上文。刚才说到我已经在NUC8里灌上了Windows Server 2019。接下来也顺利的启用了Hyper-V角色并装好了一台Ubuntu 22.04 LTS 的虚机。由于自从上回在树莓派上跑通了Paddle-Lite-Demo之后想再研究一下PaddleOCR但进展不顺&#xff0c;因此决定先不折腾了&#xff0c;还是从x6…

【论文写作】如何写科技论文?万能模板!!!(以IEEE会议论文为例)

0. 写在前面 常言道&#xff0c;科技论文犹如“八股文”&#xff0c;有固定的写作模式。本篇博客主要是针对工程方面的论文的结构以及写作链条的一些整理&#xff0c;并不是为了提高或者润色一篇论文的表达。基本上所有的论文&#xff0c;都需要先构思好一些点子&#xff0c;有…

一文搞懂Session和JWT登录认证

前言 目前在开发的小组结课项目中用到了JWT认证&#xff0c;简单分享一下&#xff0c;并看看与Session认证的异同。 登录认证&#xff08;Authentication&#xff09;的概念非常简单&#xff0c;就是通过一定手段对用户的身份进行确认。 我们都知道 HTTP 是无状态的&#xf…

强化学习技巧

此软件包处于维护模式&#xff0c;请使用Stable-Baselines3 (SB3)获取最新版本。您可以在 SB3 文档中找到迁移指南。 本节的目的是帮助您进行强化学习实验。它涵盖了有关 RL 的一般建议&#xff08;从哪里开始、选择哪种算法、如何评估算法等&#xff09;&#xff0c;以及使用自…

【Linux】System V 共享内存、消息队列、信号量

&#x1f34e;作者&#xff1a;阿润菜菜 &#x1f4d6;专栏&#xff1a;Linux系统编程 system V共享内存介绍 System V 共享内存是一种进程间通信的机制&#xff0c;它允许多个进程共享一块物理内存区域&#xff08;称为“段”&#xff09;。System V 共享内存的优点是效率高&…

OTG是什么意思?

OTG是什么意思&#xff1f; OTG是怎么样实现的&#xff1f; TYPE-C接口的手机如何实现同时充电OTG功能&#xff1f; OTG是什么意思&#xff1f; OTG是On-The-Go的缩写&#xff0c;是一项新兴技术&#xff0c;主要应用于不同的设备或移动设备间的联接&#xff0c;进行数据交…

基于遥感的自然生态环境检测——实验三:生态因子提取

实验三&#xff1a;生态因子提取 一、实验目标 生态因子生成&#xff1b;生态因子归一化&#xff1b;生态环境评价 二、实验内容 根据经过大气校正后的影像生产土地覆盖指数、土壤指数以及坡度等&#xff0c;对土地覆盖指数、土壤指数以及坡度进行密度分割归一化&#xff1…

“SCSA-T学习导图+”系列:下一代防火墙

本期引言&#xff1a; 近年来&#xff0c;随着数字化业务带给我们高效和便捷的同时&#xff0c;信息暴露面的增加、网络边界的模糊化以及黑客攻击的产业化&#xff0c;使得网络安全事件相较以往成指数级增加。传统防火墙基于五元组的方式对进出网络的数据流量进行访问控制&…

JavaScript(JS)-1.JS基础知识

1.JavaScript概念 (1)JavaScript是一门跨平台&#xff0c;面向对象的脚本语言&#xff0c;来控制网页行为的&#xff0c;它能使网页可交互 (2)W3C标准&#xff1a;网页主要由三部分组成 ①结构&#xff1a;HTML负责网页的基本结构&#xff08;页面元素和内容&#xff09;。 …

【Linux网络服务】Linux网络设置

一、查看网络配置 1.1ifconfig 1.2ip a 1.3什么是mtu 最大传输单元MTU&#xff0c;是指网络能够传输的最大数据包大小&#xff0c;以字节为单位。MTU的大小决定了发送端一次能够发送报文的最大字节数。如果MTU超过了接收端所能够承受的最大值&#xff0c;或者是超过了发送路径…