智能指针原理、使用和实现——C++11新特性(三)

目录

一、智能指针的理解

二、智能指针的类型

三、shared_ptr的原理

1.引用计数

2.循环引用问题

3.weak_ptr处理逻辑

四、shared_ptr的实现

五、定制删除器

六、源码


一、智能指针的理解

问题:什么是智能指针?为什么要有智能指针?智能指针解决什么样的问题?

要明白上面这些问题,我们先引入一个程序。

double div(double x, double y)
{
	int* p = new int(120);
	if (y == 0)
	{
		string s = "y can not be zero.";
		throw s;
	}
	delete p;
	return x / y;
}
int main()
{
	try
	{
		double x = 2, y = 0;
		div(x, y);
	}
	catch (string s)
	{
		cout << s << endl;
	}
	return 0;
}

上面这段程序,如果出现抛异常的话,p的内存是没有办法释放的。 

        智能指针简称RAll,是一种自动化管理资源的类模板,这里指的资源可以是:动态开辟的内存,文件指针,网络连接,互斥锁等等。RAII在获取资源时把资源委托给⼀个对象,接着控制对资源的访问,利用对象的⽣命周期结束时会自动析构的特性来完成对资源的自动释放,这样保障了资源的正常释放,避免资源泄漏问题。

二、智能指针的类型

       智能指针其实就是在原指针的基础上套一个类,如果就这样简单的想会有一个非常致命问题——当一块空间被两个智能指针对象指向时会析构两次,从而引发编译器报错

        库中的智能指针有很多,用法都一样,区别在于处理同一块空间会被释放两次的方法不同。接下来我们就来看这些智能指针是如何处理的。

  • auto_ptr:在做拷贝构造和赋值重载时,把原对象的资源管理权直接转移给新的对象,原对象指向空,保证一块资源只被一个智能指针管理,从而达到一块资源只被释放一次的目的。但是这种做法会使原指针悬空,如果被误用会导致访问报错的问题。推荐指数:⭐
  • unique_ptr:不允许做拷贝构造和赋值重载操作,从而达到一块资源只被释放一次的目的。如果用不到这两个函数的话可以考虑用这个智能指针。推荐指数:⭐⭐⭐⭐
  • shared_ptr:支持多个对象指向同一块资源,它底层用的是引用计数的方法,这是一个很优秀的智能指针,在下文我们会重点来学习该指针的具体原理和实现。推荐指数:⭐⭐⭐⭐⭐

三、shared_ptr的原理

1.引用计数

        原理:使用一个变量记录有几个对象在管理该资源,从而决定是否释放该资源。例如:当一个对象走到析构函数时也就是该对象生命结束了不再管理这块资源了,所以引用计数减一,此时如果引用计数变成0的话说明该资源没有对象管理了,可以直接释放,如果不是0,说明还有对象在管理,所以不用处理。

2.循环引用问题

        例如这样一个环形链表,当n1生命周期结束时发现,还有对象(n2的_next)在指向它,所以没有释放资源,当n2生命周期结束时发现还有对象(n1的_next)指向它,所以也没有释放资源。

        也可以这样想:n1的资源什么时候释放,因为n2还在用呢,需要n2的资源释放掉,那么n2的资源什么时候释放,因为n1还在用呢,需要n1的资源释放掉。循环往复始终释放不了,所以导致内存泄漏。

3.weak_ptr处理逻辑

        shared_ptr虽然很优秀,但依旧有缺陷——循环引用问题,所以就有了weak_ptr用来辅助shared_ptr来解决这个问题。

        weak_ptr不⽀持RAII,也不⽀持访问资源,所以我们看⽂档发现weak_ptr构造时不⽀持绑定到资源,只⽀持绑定shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引⽤计数,那么就可以解决上述的循环引⽤问题。

        weak_ptr没有重载operator*和operator->等,因为他不参与资源管理,那么如果他绑定的shared_ptr已经释放了资源,那么他去访问资源就是很危险的。weak_ptr⽀持expired检查指向的资源是否过期,use_count也可获取shared_ptr的引⽤计数,weak_ptr想访问资源时,可以调⽤lock返回⼀个管理资源的shared_ptr,如果资源已经被释放,返回的shared_ptr是⼀个空对象,如果资源没有释放,则通过返回的shared_ptr访问资源是安全的。

如下示例:

#include<iostream>
#include<memory>
using namespace std;
int main()
{
	shared_ptr<string> p(new string("left"));
	weak_ptr<string> pw(p);
	cout << pw.expired() << endl;
	cout << pw.use_count() << endl;
	auto p3 = pw.lock();
	cout << *p3 << endl;
	return 0;
}

四、shared_ptr的实现

计数器我们可以储存一个int*类型的指针来实现,然后需要一个指针来储存资源的地址。如下:

        构造函数:我们是要用_ptr储存资源的地址空间所以需要传入一个指针,同时需要给_count开辟空间,并且设为1。

My_share_ptr(T* ptr)
	:_ptr(ptr), _count(new int(1)){}

        拷贝构造:只需要进行资源地址的拷贝和引用计数的拷贝,然后将_count++,表示该资源的管理者增加一个。

My_share_ptr(const My_share_ptr<T>& sptr)
{
	_ptr = sptr._ptr;
	_count = sptr._count;
	(*_count)++;
}//也可以用初始化列表的方式来写

拷贝赋值:这个比较复杂它考虑到的因素有很多。

  1. 自己给自己拷贝赋值:对于这种情况,直接做一个特判后直接退出函数,不用做任何操作。
  2. 对于=的左操作对象:在被赋值前先处理一下现在手上的资源,即让_count--然后判断_count是否为0,如果是则进行资源的释放,如果不是则不用做任何处理。
  3. 进行赋值。
  4. 对于=的右操作对象:该对象被拷贝了一份说明对应资源的管理者增加,需要_count++。
My_share_ptr<T>& operator=(const My_share_ptr<T>& sptr)
{
	if (sptr._ptr == _ptr) return *this;
	if (--(*_count) == 0)
	{
		delete _ptr;
		delete _count;
		cout << "Delete1()" << endl;
	}
	_ptr = sptr._ptr;
	_count = sptr._count;
	(*_count)++;
	return *this;
}

对于其他运算符的重载比较简单,就不再多讲,我在下面的源码给出。

五、定制删除器

        在上面我们用的都是delete来释放资源,但是不够灵活,不同的资源用不同的释放方式,比如数组用delete[ ],文件资源需要用fclose来释放,否则就会出问题。所以就有了定制删除器,可以通过传入仿函数的方式指定资源的释放方式

        删除器如果通过模板参数来实例化的话会显得十分别扭。我们期望使用传参的方式传入删除器。但是又面临一个问题,我们并不知道这个仿函数是什么类型,该怎么储存呢?因为删除器是做资源释放,所以可以确定它的返回类型是void,参数是T*,那么此时此刻包装器就起到了巨大的作用。

        我们可以用function<void(T*)>类型接收并储存。同时把它做成一个全省参数,如下:

My_share_ptr(T* ptr, function<void(T*)> det= [](T* p) {delete p; })
	:_ptr(ptr),_count(new int(1)),_det(det){}

        当需要释放资源的时候不要用delete  _ptr,而是用_det(_ptr)即可。 _count是int*类型正常使用delete释放。

六、源码

#include<iostream>
#include<string>
#include<functional>
#include<memory>
using namespace std;
template<class T>
class My_share_ptr
{
public:
	My_share_ptr(T* ptr, function<void(T*)> det= [](T* p) {delete p; })
		:_ptr(ptr),_count(new int(1)),_det(det){}
	My_share_ptr(const My_share_ptr& sptr)
	{
		_ptr = sptr._ptr;
		_count = sptr._count;
		_det = sptr._det;
		(*_count)++;
	}
	My_share_ptr& operator=(const My_share_ptr& sptr)
	{
		if (sptr._ptr == _ptr) return *this;
		if (--(*_count) == 0)
		{
			_det(_ptr);
			delete _count;
			cout << "Delete1()" << endl;
		}
		_ptr = sptr._ptr;
		_count = sptr._count;
		(*_count)++;
		return *this;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}

	~My_share_ptr()
	{
		if (--(*_count) == 0)
		{
			_det(_ptr);
			delete _count;
			cout << "Delete2()" << endl;
		}
	}
private:
	T* _ptr;
	int* _count;
	function<void(T*)> _det;
};
int main()
{
	//My_share_ptr<int> p(new int(8));
	//auto p2 = p;
	//My_share_ptr<int> c = p;
	My_share_ptr<int> p1(new int(8));
	My_share_ptr<int> p2(new int(7));
	My_share_ptr<int> p3=p1;
	p1 = p2;
	return 0;
}

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

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

相关文章

基于SpringBoot和uniapp开发的医护上门系统上门护理小程序

项目分析 一、市场需求分析 人口老龄化趋势&#xff1a;随着全球及中国人口老龄化的加剧&#xff0c;老年人口数量显著增加&#xff0c;对医疗护理服务的需求也随之增长。老年人由于身体机能下降&#xff0c;更需要便捷、高效的医护服务&#xff0c;而医护上门服务恰好满足了这…

Java——并发工具类库线程安全问题

摘要 本文探讨了Java并发工具类库中的线程安全问题&#xff0c;特别是ThreadLocal导致的用户信息错乱异常场景。文章通过一个Spring Boot Web应用程序示例&#xff0c;展示了在Tomcat线程池环境下&#xff0c;ThreadLocal如何因线程重用而导致异常&#xff0c;并讨论了其他并发…

Java-异常处理机制

Java-异常处理机制 一、异常概述1、异常的抛出机制2、如何对待异常3、异常的体系结构3.1、Throwable3.2、Error和Exception3.3、编译时异常和运行时异常3.4、常见的异常有哪些&#xff1f; 二、异常的处理方式一 try-catch的使用1、过程1&#xff1a;抛2、过程2&#xff1a;抓3…

MySQL深度剖析-索引原理由浅入深

什么是索引&#xff1f; 官方上面说索引是帮助MySQL高效获取数据的数据结构&#xff0c;通俗点的说&#xff0c;数据库索引好比是一本书的目录&#xff0c;可以直接根据页码找到对应的内容&#xff0c;目的就是为了加快数据库的查询速度。 索引是对数据库表中一列或多列的值进…

canva 画图 UI 设计

起因&#xff0c; 目的: 来源: 客户需求。 目的&#xff1a; 用数据讲故事。 数据可以瞎编&#xff0c;图表一定要漂亮。 文件分享地址 读者可以在此文件的基础上&#xff0c;继续编辑。 效果图 过程: 我还是喜欢 canva. figma&#xff0c; 我用的时候&#xff0c;每每都想…

HTTP 缓存策略

文章目录 一、HTTP的缓存的过程是怎样的&#xff1f;二、什么时候触发强缓存或协商缓存强缓存ExpiresCache-Control 协商缓存 三、服务器如何判断资源是否新鲜Last-Modified/If-Modified-SinceETag/If-None-Match 四、整体缓存过程 一、HTTP的缓存的过程是怎样的&#xff1f; …

使用OkHttp进行HTTPS请求的Kotlin实现

OkHttp简介 OkHttp是一个高效的HTTP客户端&#xff0c;它支持同步和异步请求&#xff0c;自动处理重试和失败&#xff0c;支持HTTPS&#xff0c;并且可以轻松地与Kotlin协程集成。OkHttp的设计目标是提供最简洁的API&#xff0c;同时保持高性能和低延迟。 为什么选择OkHttp …

前端学习八股资料CSS(五)

更多详情&#xff1a;爱米的前端小笔记&#xff0c;更多前端内容&#xff0c;等你来看&#xff01;这些都是利用下班时间整理的&#xff0c;整理不易&#xff0c;大家多多&#x1f44d;&#x1f49b;➕&#x1f914;哦&#xff01;你们的支持才是我不断更新的动力&#xff01;找…

5个有效的华为(HUAWEI)手机数据恢复方法

5个有效的手机数据恢复方法 华为智能手机中的数据丢失比许多人认为的更为普遍。发生这种类型的丢失有多种不同的原因&#xff0c;因此数据恢复软件的重要性。您永远不知道您的智能手机何时会在这方面垮台&#xff1b;因此&#xff0c;预防总比哀叹好&#xff0c;这就是为什么众…

数据结构 (1)基本概念和术语

一、基本概念 数据&#xff08;Data&#xff09;&#xff1a; 是对客观事物的符号表示&#xff0c;在计算机科学中通常指计算机程序所处理的各种对象。数据可以是数值、字符、图像、声音等任何形式的信息。数据元素&#xff08;Data Element&#xff09;&#xff1a; 也称为数据…

SpringBoot源码解析(四):解析应用参数args

SpringBoot源码系列文章 SpringBoot源码解析(一)&#xff1a;SpringApplication构造方法 SpringBoot源码解析(二)&#xff1a;引导上下文DefaultBootstrapContext SpringBoot源码解析(三)&#xff1a;启动开始阶段 SpringBoot源码解析(四)&#xff1a;解析应用参数args 目录…

.NET桌面应用架构Demo与实战|WPF+MVVM+EFCore+IOC+DI+Code First+AutoMapper

目录 .NET桌面应用架构Demo与实战|WPFMVVMEFCoreIOCDICode FirstAutoPapper技术栈简述项目地址&#xff1a;功能展示项目结构项目引用1. 新建模型2. Data层&#xff0c;依赖EF Core&#xff0c;实现数据库增删改查3. Bussiness层&#xff0c;实现具体的业务逻辑4. Service层&am…

c++学习第三天

创作过程中难免有不足&#xff0c;若您发现本文内容有误&#xff0c;恳请不吝赐教。 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、内联函数 1.铺垫 #include<iostream>//实现一个ADD的宏函数 //#define ADD(x, y) xy //错解1 //#defin…

html 图片转svg 并使用svg路径来裁剪html元素

1.png转svg 工具地址: Vectorizer – 免费图像矢量化 打开svg图片,复制其中的path中的d标签的路径 查看生成的svg路径是否正确 在线SVG路径预览工具 - UU在线工具 2.在html中使用svg路径 <svg xmlns"http://www.w3.org/2000/svg" width"318px" height…

Solana应用开发常见技术栈

编程语言 Rust Rust是Solana开发中非常重要的编程语言。它具有高性能、内存安全的特点。在Solana智能合约开发中&#xff0c;Rust可以用于编写高效的合约代码。例如&#xff0c;Rust的所有权系统可以帮助开发者避免常见的内存错误&#xff0c;如悬空指针和数据竞争。通过合理利…

23种设计模式-访问者(Visitor)设计模式

文章目录 一.什么是访问者模式&#xff1f;二.访问者模式的结构三.访问者模式的应用场景四.访问者模式的优缺点五.访问者模式的C实现六.访问者模式的JAVA实现七.代码解释八.总结 类图&#xff1a; 访问者设计模式类图 一.什么是访问者模式&#xff1f; 访问者模式&#xff08;…

RecyclerView详解——(四)缓存复用机制

稍微看了下源码和部分文章&#xff0c;在此做个小小的总结 RecyclerView&#xff0c;意思为可回收的view&#xff0c;那么相对于listview&#xff0c;他的缓存复用肯定是一大优化。 具体而言&#xff0c;当一个列表项被移出屏幕后&#xff0c;RecyclerView并不会销毁其视图&a…

C++设计模式行为模式———迭代器模式

文章目录 一、引言二、迭代器模式三、总结 一、引言 迭代器模式是一种行为设计模式&#xff0c; 让你能在不暴露集合底层表现形式 &#xff08;列表、 栈和树等&#xff09; 的情况下遍历集合中所有的元素。C标准库中内置了很多容器并提供了合适的迭代器&#xff0c;尽管我们不…

【网络云计算】2024第48周-技能大赛-初赛篇

文章目录 1、比赛前提2、比赛题目2.1、 修改CentOS Stream系统的主机名称&#xff0c;写出至少3种方式&#xff0c;并截图带时间戳和姓名&#xff0c;精确到秒&#xff0c;否则零分2.2、 创建一个名为你的名字的拼音的缩写的新用户并设置密码&#xff0c;将用户名添加到 develo…

【汇编语言】数据处理的两个基本问题(三) —— 汇编语言的艺术:从div,dd,dup到结构化数据的访问

文章目录 前言1. div指令1.1 使用div时的注意事项1.2 使用格式1.3 多种内存单元表示方法进行举例1.4 问题一1.5 问题一的分析与求解1.5.1 分析1.5.2 程序实现 1.6 问题二1.7 问题二的分析与求解1.7.1 分析1.7.2 程序实现 2. 伪指令 dd2.1 什么是dd&#xff1f;2.2 问题三2.3 问…