学习->C++篇十九:四种智能指针及其实现

news2024/11/20 9:48:45

目录

为什么需要智能指针?

什么是内存泄露?

如何避免内存泄露?

什么是RAII?

RAII有什么用?

智能指针的原理是什么?

C++的智能指针有哪些?

auto_ptr

unique_ptr

shared_ptr

weak_ptr


为什么需要智能指针?

先看下面的代码:

        如果fun()函数抛出了异常,根据上篇异常的知识,当前代码块没有捕获异常,那么异常就会到上一个函数调用栈,也就是调用test函数的代码块匹配异常,那么m1和m2指向的空间就没有人释放,于是会造成内存泄露。

什么是内存泄露?

        因为疏忽或者错误,进程不能释放已经不再使用的内存的情况。

显然内存泄漏是不能容忍的,因为长期运行的进程,如果出现内存泄露,会影响很大,导致系统响应越来越慢,最终卡死,因为内存空间不断的减少。比如操作系统,后台服务。

内存泄露的种类:

    堆内存的泄漏

    系统资源的泄露,比如套接字,文件描述符,管道

如何避免内存泄露?

    - 编码规范,简单而言就是申请的内存务必记得释放,但从上面的代码可看出,如果遇到了异常也不能解决异常导致的问题,于是需要下面的方法。

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

    - 使用内存管理库

    - 使用内存泄露检测工具

什么是RAII?

RAII:即Resource Acquisition Is Initialization资源获取即初始化,一种利用对象的生命周期来控制资源的技术。注意,这个思想是后面实现智能指针的原理。

RAII有什么用?

- 不需要显式释放资源

- 对象所需要的资源在他的生命周期内中保持有效

智能指针的原理是什么?

1.利用RAII特性:让对象的生命周期去控制我们资源的生命周期

2.面向对象思想,智能指针用类实现,需要重载运算符让智能指针有像指针一样的行为

C++的智能指针有哪些?

基于不同的场景,智能指针的实现和使用不同将智能指针进行分类。

(注意:实现不同的本质是管理指针的方法不同,这里主要体现在如何拷贝智能指针对象上)

auto_ptr

C++98版本提供了auto_ptr智能指针

- 它的拷贝构造函数和赋值重载函数是基于管理权转移的思想实现的

实现如下:

template <class T>//智能指针要管理不同类型的指针,实现为类模板
class auto_ptr//基于管理权转移的思想
{
	typedef auto_ptr<T> self;
public:
	auto_ptr(T* ptr)
		:_ptr(ptr)
	{}

	auto_ptr(self& s)
		:_ptr(s._ptr)//转移指针
	{
		s._ptr = nullptr;
	}

	self& operator=(self& s)
	{
		if (_ptr)delete _ptr;//直接释放原指针
		_ptr = s._ptr;//转移指针
		s._ptr = nullptr;
        return *this;
	}
	
	~auto_ptr()
	{
		if (_ptr)
			delete _ptr;
	}

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

C++11参考了boost库中智能指针的实现,实现了三种智能指针:

unique_ptr

unique_ptr基于防拷贝的思路,直接使用关键字delete删除拷贝构造函数和赋值重载函数

实现如下:

template <class T>//智能指针要管理不同类型的指针,实现为类模板
class unique_ptr//基于禁止拷贝的思想
{
	typedef unique_ptr<T> self;
public:
	unique_ptr(T* ptr = nullptr)
		:_ptr(ptr)
	{}

	unique_ptr(const self& s) = delete;
	self& operator=(const self& s) = delete;

	~unique_ptr()
	{
		if (_ptr)
			delete _ptr;
	}

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

shared_ptr

可以拷贝的智能指针,shared_ptr利用引用计数,记录指向资源的指针数目,对象析构的时候计数减一,当引用计数为0的时候才释放资源,于是可以实现如下:

template <class T>//智能指针要管理不同类型的指针,实现为类模板
class shared_ptr//利用引用计数实现允许拷贝的智能指针
{
	typedef shared_ptr<T> self;
public:
	shared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		,_pcount(new int(1))
	{}

	shared_ptr(self& s)
		:_ptr(s._ptr)
		,_pcount(s._pcount)
	{
		++(*_pcount);
	}

	self& operator=(const self& s)
	{
		if (_ptr != s._ptr)
		{
			if (--(*_pcount) == 0 && _ptr && _pcount)//当前指针计数--,s的指针计数++
            {
                delete _pcount;
				delete _ptr;
            }
			_ptr = s._ptr;
			_pcount = s._pcount;
			++(*_pcount);
		}
        return *this;
	}

	~shared_ptr()
	{
		if (--(*_pcount) == 0 && _ptr && _pcount)
		{
			delete _ptr;
			delete _pcount;
		}
	}

	T* operator->() { return _ptr; }
	T operator*() { return *_ptr; }
	int use_count() { return *_pcount; }//返回引用计数
	T* get() const { return _ptr; }//返回指针
private:
	T* _ptr;
	int* _pcount;//引用计数,当不同对象指针相同时,
	//其计数的地址需要一样,计数大小才能同步,故存储在堆空间
};

因为引用计数存储在堆空间,改变引用计数的函数,有线程安全问题,为了保持线程安全,底层要有互斥锁。

于是可以实现线程安全的版本如下:

#include <mutex>
using std::mutex;
template <class T>//智能指针要管理不同类型的指针,实现为类模板
class shared_ptr//利用引用计数实现允许拷贝的智能指针
{
	typedef shared_ptr<T> self;
public:
	shared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		,_pcount(new int(1))
		,_pmutex(new mutex)
	{}

	shared_ptr(const self& s)
		:_ptr(s._ptr)
		,_pcount(s._pcount)
		,_pmutex(s._pmutex)
	{
		(*_pmutex).lock();
		++(*_pcount);
		(*_pmutex).unlock();
	}

	void Release()
	{
		(*_pmutex).lock();
		bool flag = false;//判断是否需要释放锁,因为这里不能在临界区释放锁
		if (--(*_pcount) == 0 && _ptr && _pcount)//当前指针计数--,s的指针计数++
		{
			delete _ptr;
			delete _pcount;
			flag = true;
		}
		(*_pmutex).unlock();

		if (flag)
			delete _pmutex;
	}

	self& operator=(const self& s)
	{
		if (_ptr != s._ptr)
		{
			Release();
			_ptr = s._ptr;
			_pcount = s._pcount;
			_pmutex = s._pmutex;
			(*_pmutex).lock();
			++(*_pcount);
			(*_pmutex).unlock();
		}
		return *this;
	}

	~shared_ptr()
	{
		Release();
	}

	T* operator->() { return _ptr; }
	T operator*() { return *_ptr; }
	int use_count() { return *_pcount; }//返回引用计数
	T* get() const { return _ptr; }//返回指针
private:
	T* _ptr;
	int* _pcount;//引用计数,当不同对象指针相同时,
	//其计数的地址需要一样,计数大小才能同步,故存储在堆空间
	mutex* _pmutex;//保证线程安全
};

注意:智能指针内部实现了加锁,故是线程安全的,但是指针指向的资源不是线程安全的,需要访问的人处理。

weak_ptr

什么是循环引用问题?

比如如下代码:

struct listnode
{
	shared_ptr<listnode> _prev=shared_ptr<listnode>(nullptr);
	shared_ptr<listnode> _next= shared_ptr<listnode>(nullptr);
	int _data = 0;
	~listnode()
	{
		std::cout << "~listnode" << std::endl;
	}
};

int main() 
{
	shared_ptr<listnode>n1 = new listnode;
	shared_ptr<listnode>n2 = new listnode;
	n2->_prev = n1;
	n1->_next = n2;
	return 0;
}

节点中两个指针用智能指针来实现,当节点相内部指针互指向的时候,节点的引用计数都增加到2,析构时智能指针释放,节点引用计数都减为1,所以两个节点的资源都不会被释放,会造成内存泄露,如图:

 

        为了解决shared ptr指针循环引用的问题,引入weak_ptr,其拷贝构造函数和赋值重载函数参数是shared_ptr类型的对象,拷贝时不会增加其底层的引用计数,即不参与管理我们的资源。上述循环引用问题,只需要将节点内成员shared_ptr 改成 weak_ptr 即可解决。

实现如下:

template <class T>//智能指针要管理不同类型的指针,实现为类模板
class weak_ptr//直接拷贝shared_ptr 的指针,不增加引用计数->不参与资源的管理
{
public:
	weak_ptr(T* ptr = nullptr)
		:_ptr(ptr)
	{}

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

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

	~weak_ptr()
	{}

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

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

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

相关文章

【第十五章 分库分表(垂直拆分,水平拆分),MyCat】

第十五章 分库分表&#xff08;垂直拆分&#xff0c;水平拆分&#xff09;&#xff0c;MyCat 1.分库分表&#xff1a; &#xff08;1&#xff09;介绍&#xff1a; ①采用单数据库进行数据存储&#xff0c;存在以下性能瓶颈&#xff1a; A.IO瓶颈&#xff1a;热点数据太多&…

第二证券|A股传奇董秘变身老板,公司IPO过会

A股传奇董秘何愿平卷土重来&#xff0c;但这次不是以董秘的身份。 12月20日&#xff0c;碧兴科技IPO获科创板上市委会议经过&#xff0c;其实控人正是A股曾经的传奇董秘何愿平。作为前碧水源董秘&#xff0c;何愿平曾多年连任A股身家最贵董秘。 二次创业过会 招股书显现&…

离散化算法

目录 算法原理 算法模板 1&#xff09;手工编码 2&#xff09;c用STL函数实现离散化 附录&#xff1a; 算法原理 给出一列数字&#xff0c;在有些情况下&#xff0c;这些数字的值得绝对大小不重要&#xff0c;而相对大小很重要。例如&#xff0c;对一个班级学生的成绩进行…

图片加水印怎么弄?这些图片加水印方法分享给你

相信很多小伙伴平常会喜欢将自己拍摄的作品上传到社交平台上与他人分享吧。然而有时会遇到自己辛苦拍摄的视频或者图片被他人盗用&#xff0c;甚至是获利的情况。其实想要避免自己的作品被他人盗用&#xff0c;我们可以在作品上面添加专属自己的水印&#xff0c;不仅可以给作品…

Java学生成绩管理系统

1、内容要求 编写一个学生成绩管理系统。学生的属性包括学号、姓名、年龄等。每个学生要学习若干课程&#xff0c;每门课程有平时成绩、期中考试成绩、期末考试成绩以及总评成绩&#xff0c;其中总评成绩平时成绩*30%期中成绩*30%期末成绩*40%。请先设计合理的类存放这些信息&a…

Span-level Bidirectional Network(2022 EMNLP)

论文题目&#xff08;Title&#xff09;&#xff1a;A Span-level Bidirectional Network for Aspect Sentiment Triplet Extraction 研究问题&#xff08;Question&#xff09;&#xff1a;方面情感三元组提取(ASTE)是一种新的细粒度情感分析任务&#xff0c;旨在从评论句中提…

【ML】机器学习中的十大深度学习算法

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

RTOS多任务切换实现

实现任务需要的基础知识 1、程序内部细节 通过分析C语言程序的编码会发现程序都是一些指令和数据。 什么是程序&#xff1f; 指令运行过程中的数据 2、常用汇编指令 汇编指令详解 3、ARM架构过程调用标准AAPCS 传参&#xff1a; 通过r0-r3传递&#xff0c;多于4个参数的部…

【矩阵论】6. 范数理论——基本概念——向量范数与矩阵范数

6.1 基本概念 6.1.1 向量范数 a. 模长&#xff08;二范数&#xff09; Cn中向量X(x1x2⋮xn)的模长为∣X∣(X,X)tr(AHA)∣x1∣2∣x2∣2⋯∣xn∣2C^n中向量 X\left( \begin{matrix} x_1\\x_2\\\vdots\\x_n \end{matrix} \right)的模长为 \vert X\vert\sqrt{(X,X)}\sqrt{tr(A^HA…

nginx+keeplived 实现高可用 Web 负载均衡

nginxkeeplived 实现高可用 Web 负载均衡 一、架构简介 在系统设计中&#xff0c;可以利用Nginx的反向代理和负载均衡实现后端应用的高可用性&#xff0c;同时我们还需要考虑Nginx的单点故障。如果Nginx所在服务器宕机&#xff0c;或者Nginx服务不可用就会造成整个系统的不可…

鸡蛋车的控制律

这个老头像幽灵&#xff0c; ​拿个鸡蛋来炸群。 ​只见动画不见车&#xff0c; ​骗人无果还丢人&#xff01; 温州一个做汽车驾驶模拟器的&#xff0c;在重庆为我约了美女&#xff0c;为鸡蛋车站台。他掏空口袋砸我&#xff0c;材料、工资、车马&#xff0c;都是他担。 余姚一…

代码随想录算法训练营第十天(字符串)| 232.用栈实现队列,225. 用队列实现栈

代码随想录算法训练营第九天&#xff08;字符串&#xff09;| 232.用栈实现队列&#xff0c;225. 用队列实现栈 232. 用栈实现队列 大家可以先看视频&#xff0c;了解一下模拟的过程&#xff0c;然后写代码会轻松很多。 题目链接/文章讲解/视频讲解&#xff1a; 看到题目的…

react笔记_08生命周期

目录生命周期(旧)生命周期componentWillMountcomponetdidMountshouldComponentUpdatecomponentWillUpdatecomponentDidUpdatecomponentWillUnmountcomponentWillReceiveProps组件的挂载、更新、销毁案例1渲染过程更新过程1-通过setState去修改数据更新过程2-通过forceUpdate强制…

用Python分析《阿凡达·水之道》的豆瓣短评

《阿凡达水之道》于2022年12月16日上映。第一部的口碑、评分等都非常高&#xff0c;第二部是否能延续呢&#xff0c;本文获取了该电影的豆瓣短评&#xff0c;进行了初步的分析&#xff0c;看下观众都是如何评价的。 数据获取 打开豆瓣首页&#xff0c;搜索电影名&#xff0c;进…

Spring(三): 使用注解来存储和读取Bean对象

目录一、存储Bean对象1.1 配置扫描路径1.2 使用注解存储Bean对象1.3 通过上下文读取Bean对象1.4 Bean命名规则1.5 方法注解 Bean1.5 重命名Bean二、获取Bean对象2.1 属性注入2.2 Setter注入2.3 构造方法注入2.4 Resource注解2.5 Resource注解中name参数的作用一、存储Bean对象 …

说明书丨艾美捷Annexin-V-Cy3凋亡检测试剂盒

Cy3标记的重组人膜联蛋白V显示亮红色荧光&#xff08;Ex&#xff08;max&#xff09;:543nm&#xff1b;Em&#xff08;最大值&#xff09;&#xff1a;570nm&#xff09;。 艾美捷Annexin-V-Cy3凋亡检测试剂盒化学性质&#xff1a; Applications: Flow Cytometry, Fluorescen…

C++【修理之路】初识string

这里写目录标题为什么学习string类&#xff1f;标准库中的string类string的定义转换为C风格的字符串string类的输出和输出访问字符串中的字符字符串的拼接string 字符串的增删改查总结为什么学习string类&#xff1f; C语言中&#xff0c;字符串是以’\0’结尾的一些字符的集合…

3D数学之四元数 学习笔记

四元数有三个虚部&#xff0c;一个实部 [ w (x y z) ] w xi yj zk i j k ijk -1 用于表示&#xff0c;物体在空间中的任意角度旋转 四元数的模 Sqrt(pow(w, 2) pow(x, 2) pow(y, 2) pow(z, 2)) 四元数共轭&#xff1a;p a bi 共轭为&#xff1a; p a - bi …

一文梳理 | 电力企业网络安全管理及等级保护工作重点

前言 为加强电力行业网络安全监督管理&#xff0c;规范电力行业网络安全工作&#xff0c;国家能源局近日印发《电力行业网络安全管理办法》和《电力行业网络安全等级保护管理办法》&#xff08;以下简称“两办法”&#xff09;&#xff0c;有效期均为5年。随着这两部规范性文件…

门神 马丁内斯 要什么样的成就,才不枉这些年的颠沛流离

2022-12-18日 随着劳塔罗的最后一球波网而入 阿根廷成功获得了卡塔尔世界杯的冠军 结束后 所有人都去拥抱劳塔罗 只有梅西拉起了倒在地上的马丁内斯 很多人说 在这次比赛中 阿根廷有两个神 一个是球王梅西 另一个则是 门将 马丁内斯 当场上二比零时 所有人都以为阿根廷胜券在…