智能指针(2)

智能指针(2)

  • shared_ptr(共享型智能指针)
    • 基础知识
      • 特点
      • 引用计数器
      • 共享型智能指针结构理解
    • shared_ptr仿写
      • 删除器类
      • 计数器类
      • shared_ptr类
      • 使用以及仿写代码的理解
  • 循环引用
    • _Weaks
  • 初始化智能指针的方法

shared_ptr(共享型智能指针)

基础知识

在java中有一个垃圾回收器,可以运用到所有资源,heap内存和系统资源都可以使用的系统,而C++11中的shared_ptr就是为了达到这样的目的,其和唯一性智能指针不同,共享型智能指针没有一个特定的指针拥有资源对象,而是这些指针指向同一资源对象,相互协作来管理该对象,在不需要时进行析构。

特点

  • 共享所有权模式
  • 使用了引用技术控制管理资源对象的生命周期
  • 提供拷贝构造函数和赋值重载函数;提供移动构造和移动赋值。
  • 添加了删除器类型
  • 在容器中保存shared_ptr对象是安全的
  • 重载了operator->和operator*运算符,因此可以像普通指针一样使用

引用计数器

引用计数器的值为指向资源的智能指针数,当智能指针对象死亡时,判断引用计数器的值,如果不是1,智能指针本身析构,不释放资源,如果是1,析构智能指针,释放资源。

共享型智能指针结构理解

此处用自己设计的整型举例,不做编写

int main() {
	std::shared_ptr<Int> pa(new Int(10));
	cout << pa.use_count() << endl;
	std::shared_ptr<Int>pb(pa);
	cout << pa.use_count() << endl;
	std::shared_ptr<Int> pc;
	pc = pa;
	cout << pa.use_count() << endl;
	return 0;
}

其输出结果是这样的:
在这里插入图片描述

为什么会输出这样的结果呢?我们可以画图来理解智能指针的结构。
在这里插入图片描述
这里我只画了pc对象的结构,其结构都是一样的,创建pa对象的时候,其有三个成员对象,一个是删除器,另外两个是指针,指向堆区的空间,其中一个指针指向我们的Int对象,另一个指向我们在堆区创建的计数器对象,计数器中存在一个指针和两个计数器,其指针指向我们的Int对象,两个计数器分别是_Uses和_Weaks,这里个计数器分别是共享型智能指针和弱引用智能指针。这里暂时不给大家说说这两者的区别,后面会在循环引用中讲解,大家只需要知道当创建智能指针时,其资源数+1。所以呢我们输出结果才是123。
这里如果大家还不是很懂,我们再对其进行简单的仿写。

shared_ptr仿写

删除器类

删除器类型就是为了通过仿函数来析构对象,增强代码的可用性。

template<class _Ty>
struct my_deleter
{
	void operator()(_Ty* ptr) const
	{
		delete ptr;
	}
};
template<class _Ty>
struct my_deleter<_Ty[]>
{
	void operator()(_Ty* ptr) const
	{
		delete[]ptr;
	}
};

计数器类

class My_RefCount
{
public:
	using element_type = _Ty;
	using pointer = _Ty*;
private:
	_Ty* _Ptr;
	std::atomic<int> _Uses; // shared;
	std::atomic<int> _Weaks; // weak_ptr;
public:
	My_RefCount(_Ty* ptr = nullptr)
		:_Ptr(ptr), _Uses(0), _Weaks(0)
	{
		if (_Ptr != nullptr)
		{
			_Uses = 1;
			_Weaks = 1;
		}
	}
	~My_RefCount() = default;
	void Incref()
	{
		_Uses += 1;
	}
	void Incwref()
	{
		_Weaks += 1;
	}
	int Decref()
	{
		if (--_Uses == 0)
		{
			Decwref();
		}
		return _Uses;
	}
	int Decwref()
	{
		_Weaks -= 1;
		return _Weaks;
	}
	int _use_count() const
	{
		return _Uses.load();
	}
};

在计数器类型中成员方法就是将两个计数器进行+1,-1操作,返回当前指向资源的计数器。也没有太多要说的,主要是其结构,上面说了计数器类型,其有三个成员对象,分别是ptr指针,指向资源对象,而另外两个便是整型计数器,但是其写法是这样的 std::atomic<int> _Uses; // shared; std::atomic<int> _Weaks; // weak_ptr; 这样的写法是让其这两个计数器具有原子性。而原子性是什么呢?原子性:我们发现在多线程编程中,我们举一个例,有三个线程,和一个全局变量a=1,每个线程中对该变量进行++操作100次,我们可以发现结果有可能不是300,这是因为有几个线程同时对变量+1,导致两次+1操作本来+2,变成了+1一次,所以呢会小于300,有了原子性,便不会出现这样的操作,在++的时刻不会出现其他线程也+1操作。和锁的作用类似,但是大家对互斥锁的底层有所了解的话会知道其是从就绪态,运行态,阻塞态不停的切换,这样的话比该方法效率低得多。

shared_ptr类

template<class _Ty, class Deleter = my_deleter<_Ty> >
class my_shared_ptr
{
private:
	_Ty* mPtr;
	My_RefCount<_Ty>* mRep;
	Deleter mDeleter;
public:
	my_shared_ptr(_Ty* ptr = nullptr) :mPtr(ptr), mRep(nullptr)
	{
		if (mPtr != nullptr)
		{
			mRep = new My_RefCount(mPtr);
		}
	}
	my_shared_ptr(const my_shared_ptr& src) :mPtr(src.mPtr), mRep(src.mRep)
	{
		if (mRep != nullptr)
		{
			mRep->Incref();
		}
	}
	~my_shared_ptr()
	{
		if (mRep != nullptr && mRep->Decref() == 0)
		{
			mDeleter(mPtr);
			delete mRep;
		}
		mPtr = nullptr;
		mRep = nullptr;
	}
	my_shared_ptr(my_shared_ptr&& right) :
		mPtr(right.mPtr), mRep(right.mRep) {
		right.mPtr = nullptr;
		right.mRep = nnullptr;
	}

	/*my_shared_ptr& operator=(const my_shared_ptr& src) {
		if (this == &src) return*this;
		if (mRep != nullptr && mRep->Decref() == 0) {
			mDeleter(mPtr);
			delete mRep;
		}
		mPtr = src.mPtr;
		mRep = src.mRep;
		if (mRep != nullptr) {
			mRep->Incref();
		}
		return *this;
	}*/
	my_shared_ptr& operator=(const my_shared_ptr& src) {
		my_shared_ptr(src).swap(*this);
	}
	/*my_shared_ptr& operator=(my_shared_ptr&& right) {
		if (this == &right) return *this;
		if (mRep != nullptr && mRep->Decref() == 0) {
			mDeleter(mPtr);
			delete mRep;
		}
		mPtr = right.mPtr;
		mRep = right.mRep;
		right.mPtr = nullptr;
		right.mRep = nullptr;
	}*/
	my_shared_ptr& operator=(my_shared_ptr&& right) {
		if (this == &right) return *this;
		my_shared_ptr(std::move(right)).swap(*this);
		return*this;
	}
	int use_count() const {
		return mRep != nullptr ? mRep->_use_count() : 0;
	}

	_Ty* get()const {
		return mPtr;
	}

	_Ty& operator*()const {
		return *get();
	}
	_Ty* operator->()const {
		return get();
	}

	operator bool()const {
		return mPtr != nullptr;
	}
	void swap(my_shared_ptr& other) {
		std::swap(this->mPtr, other.mPtr);
		std::swap(this->mRep, other.mRep);
	}

	void reset() {
		if (mRep->Decref() == 0) {
			mDeleter(mPtr);
			delete mRep;
		}
		mPtr = nullptr;
		mRep = nullptr;
	}
	void reset(_Ty* p) {
		if (nullptr == p) {
			reset();
		}
		if (mRep->Decref() == 0) {
			mDeleter(mPtr);
			delete mRep;
		}
		mPtr = p;
		mRep = new My_RefCount(ptr);
	}

};

使用以及仿写代码的理解

构造函数:共享型指针的时候呢,使用指向资源的指针初始化对象中的指针,然后用该指针构建计数器对象。
拷贝构造:将原本智能指针类型的两个指针进行赋值,然后对计数器中的智能指针计数器+1操作。
析构函数:如果指向对象的指针不为nullptr并且其计数器自减之后不为0,将其两个指针置为nullptr,如果自减之后为0则进行释放资源对象和计数器对象。
移动构造:将其指针进行赋值然后将形参的两个指针置为nullptr
重载赋值运算符:这里呢有两种方法,第一种是判断原本对象自减之后是否为0,然后进行释放资源,然后将待赋值对象进行赋值,如果对象不为nullptr,则计数器+1,另一种用到了置换函数,也是共享型智能指针中源码的写法,很是奇妙。使用形参创建一个临时对象,然后将其和this指向的对象进行资源置换。创建的临时对象,现在存放的是 this指向原本的资源,然后因为是临时对象,所以最后会析构一次,调用析构函数,判断其计数器是否为0来决定其是否释放对象,而创建src对象的时候便对其计数器进行了+1操作,然后资源置换给了this指针。
移动语义的赋值运算符重载:这里和重载赋值运算符大同小异,大家可以试着自行理解,同样也有和上面一样创建临时对象的写法,可以试着理解。
reset(_Ty p)*:该函数是资源覆盖,如果p为nullptr,直接调用无参的reset函数来释放该智能指针对象。如果不为nullptr,则先判断计数器自减之后为否为0来决定是否释放资源,然后使用参数指针来构造新的计数器对象。

循环引用

class Child;

class Parent
{
public:
 std::shared_ptr<Child> child;
 Parent() { cout << "Parent" << endl; }
 ~Parent() { cout << "~Parent" << endl; }

 void Hi() { cout << "hello Parent" << endl; }
};
class Child
{
public:
 std::shared_ptr<Parent> parent;
 Child() { cout << "Child" << endl; }
 ~Child() { cout << "~Child" << endl; }
};

int main()
{
 std::shared_ptr<Parent> pa(new Parent());
 std::shared_ptr<Child> pc(new Child());
 pa->child = pc;
 pc->parent = pa;

 return 0;
}

运行上面代码我们会发现两个对象都没有析构,这是为什么呢?我们画图来理解:
在这里插入图片描述
创建pa对象时,其两个指针分别指向临时的智能指针对象child,计数器对象,而计数器child对象种也有两个指针都为nullptr,然后船舰pc对象时也是样的,接着将pc赋值给pa的智能指针child对象,这样pc对象的计数器智能指针计数器+1,变成了2,然后呢pa也赋值给了pc的智能指针parent对象,导致两者析构的时候都对计数器进行了自减操作,但是自减之后都不为0所以不能析构。而怎么解决这个问题呢?

_Weaks

这就说到了我们的_Weaks弱指针计数器,这时候我们需要在pa和pc类种用弱引用智能指针(weak_ptr),其+1是对_Weaks计数器进行+1,这样就会可以析构对象了,但是有人会说那么弱智能指针的计数器不为0,那怎么办呢?
实际上这两个计数器控制的是两块堆内存,如果智能指针计数器为0,释放指向资源对象的堆内存,两个计数器都为0时释放计数器的堆内存。

int main()
{
	std::shared_ptr<Parent> pa(new Parent());
	std::shared_ptr<Child> pc(new Child());
	pa->child = pc;
	pc->parent = pa;
	pc->parent.lock()->Hi();

	return 0;
}

weak_ptr没有重载operator->和operator*操作符,因此不可以直接通过weak_ptr对象,典型的用法是调用lock函数来获得shared_ptr实例,进而访问对象。
在用了弱引用智能指针怎么调用成员函数呢?
我们发现上面代码中调用了一个lock()函数,因为弱引用智能指针没有重载->和解引用,为此通过调用lock函数获得shared_ptr,判断其共享型引用计数器是否为0来判断是否可以访问原始对象。

初始化智能指针的方法

初始化有下面两种方法:

int main()
{
	std::shared_ptr<int> ip(new int(10));//A
	map<string, std::shared_ptr<int>> it;
	it["xxin"] = std::make_shared<int>(new int(200));//B
	it["lxin"] = std::make_shared<int>(new int(100));
	return 0;
}

这两种方法呢也各有不同,B的速度比A块,但是呢也有坏处,为什么呢?
我们知道智能指针在堆区申请了两块空间,是两次申请的,而每一次申请都很费时间,而B方法申请是一次性申请够两块内存,比A方法申请两次效率高。我们知道在智能指针计数器为0时释放资源对象,弱引用智能指针计数器也为0时释放计数器,很明显有时这两块资源不能一起释放,当资源对象内存较小时可以等待弱引用智能指针计数器为0一起释放,但当内存很大时等待其结束释放便时很效率很低的。(一次申请的堆内存智能一次释放完,不能分多次释放)。

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

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

相关文章

Hive | 报错锦集

知识目录 一、写在前面✨二、Hive启动hiveserver2报错&#x1f525;三、HiveServer2启动方式✨四、Hive执行SQL语句报一大堆日志&#x1f349;五、Hive使用Load加载数据报错&#x1f36d;六、Hive执行含Count的SQL语句报错&#x1f349;七、Hive执行SQL语句报/bin/java&#x1…

openGauss5.0之学习环境 Docker安装

文章目录 0.前言1. 准备软硬件安装环境1.1 软硬件环境要求1.2 修改操作系统配置1.2.1 关闭操作系统防火墙 1.3 设置字符集参数1.4 设置时区和时间&#xff08;可选&#xff09;关闭swap交换内存1.5 关闭RemoveIPC1.6 关闭HISTORY记录 2. 容器安装2. 1支持的架构和操作系统版本2…

ChatGPT+小红书的8种高级玩法

掌握了这套万能命令&#xff0c;让你快速做出小红书爆款文案! 一、用ChatGPT做定位 我是一个大龄的普通人&#xff0c;没有什么特殊的技能&#xff0c;接下来&#xff0c;请你作为一位小红书的账号定位专家&#xff0c;通过与我对话的方式&#xff0c;为我找到我的小红书账号定…

记录一个Invalid bound statement (not found)问题

SpringBootMyBatisPlus项目&#xff0c;非常简单&#xff0c;没有任何业务逻辑&#xff1a; 1. pom文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.…

Java8 Stream详解及结束操作方法使用示例(三)

结束操作是指结束 Stream 该如何处理的操作&#xff0c;并且会触发 Stream 的执行。下面是一些常用的结束操作方法。结束操作会对数据源进行遍历&#xff0c;因此是及早求值的。 Java8 Stream详解及中间操作方法使用示例&#xff08;一&#xff09; ​​​​​​​Java8 Strea…

java生成、识别条形码和二维码

一、概述 使用 zxing 开源库 Zxing主要是Google出品的&#xff0c;用于识别一维码和二维码的第三方库主要类:BitMatrix 位图矩阵MultiFormatWriter 位图编写器MatrixToImageWriter 写入图片 可以生成、识别条形码和二维码 内置三种尺寸&#xff1a;enum Size {SMALL, MIDDLE, …

华为OD机试真题 JavaScript 实现【求符合要求的结对方式】【2023Q1 100分】,附详细解题思路

一、题目描述 用一个数组A代表程序员的工作能力&#xff0c;公司想通过结对编程的方式提高员工的能力&#xff0c;假设结对后的能力为两个员工的能力之和&#xff0c;求一共有多少种结对方式使结对后能力为N。 二、输入描述 6 2 3 3 4 5 1 6 第一行为员工的总人数&#xff…

计算机视觉-目标检测(一):从 R-CNN 到 Faster R-CNN

文章目录 1. 概要2. 区域卷积卷积神经网络R-CNN2.1 模型结构2.2 Selective Search2.3 warp2.4 R-CNN训练2.5 R-CNN推理2.6 R-CNN性能评价2.7 R-CNN的缺点 3. SPP-Net3.1 SPP-Net对RCNN的改进3.2 SPP-Net网络结构3.3 SPP-Net训练过程3.4 SPP-Net的问题 4. Fast R-CNN4.1 Fast R-…

河北沃克HEGERLS仓储货架生产厂家|夹抱式伸缩货叉四向穿梭车新型物流机器人

众所周知仓库作业主要是围绕存取、搬运、拣选、输送分拣而进行的&#xff0c;而随着物流作业的多样化、复杂化&#xff0c;四向穿梭车作为新的存储技术&#xff0c;以其灵活、柔性等特点而备受瞩目。河北沃克在成功研发四向穿梭车的基础上又对其进行了产品的横向发展。目前&…

微服务_fegin

Feign服务调用 是客户端组件 ruoyi系统中Log\Auth\User用了远程服务调用&#xff0c;用工厂模式给他的报错加了层工厂类&#xff0c;return错误的时候重写了以下方法。 在ruoyi-common-core模块中引入依赖 <!-- SpringCloud Openfeign --><dependency><group…

python语法-数据可视化(全球GDP动态柱状图开发)

python数据可视化&#xff08;全球GDP动态柱状图开发&#xff09; 开发工具&#xff1a;pycharm、pyecharts模块 &#xff08;项目数据见文末参考内容&#xff09; """ 演示GDP动态柱状图开发 """from pyecharts.charts import Bar,Timeline fr…

ExtractOfficeContent: 提取Office文件中文本、表格和图像

引言 最近有空写了一下这个库&#xff0c;用来提取Office文件中的文本和图像内容&#xff0c;用作后续整理训练语料使用。最新更新请移步&#xff1a;Github Extract Office Content Use Installextract_office_content$ pip install extract_office_contentRun by CLI. Ext…

数据可视化大屏人员停留系统的开发实录(默认加载条件筛选、单击加载、自动刷新加载、异步加载数据)

项目需求 录入进入房间的相关数据&#xff1b;从进入时间开始计时&#xff0c;计算滞留房间的时间&#xff1b;定时刷新数据&#xff0c;超过30分钟的人数&#xff0c;进行红色告警&#xff1b; 实现流程 为了完整地实现上述需求&#xff0c;我们可以按照以下步骤开发&#…

JAVA面向对象(三)

第三章 封装与继承 目录 第三章 封装与继承 1.1.封装 1.2.包 1.3.访问权限控制 1.4.static修饰符 1.4.1.成员变量 1.4.2.成员方法 1.4.3.代码块 总结 内容仅供学习交流&#xff0c;如有问题请留言或私信&#xff01;&#xff01;&#xff01;&#xff01;&#xff0…

【计算机组成与体系结构Ⅰ】知识点整理

第一章 计算机系统概论 1.1 从源文件到可执行文件 .c源程序、.i源程序、.s汇编语言程序、.o可重定位目标程序、可执行目标程序&#xff1b;后两个为二进制&#xff0c;前面为文本 1.2 可执行文件的启动和执行 冯诺依曼结构计算机模型的5大基本部件&#xff1a;运算器、控制…

技巧:win10的另一种美化字体的方式,使用noMeiryoUI

目录 1. 前提2. 字体选择3. 查看已经安装的字体并查看效果4. 安装软件修改系统字体5. 修改浏览器字体 1. 前提 21年的时候写了一篇文章&#xff0c;《Windows10下美化字体&#xff0c;达到类似mac的效果》&#xff0c;当时还很迷恋macType这个软件的使用&#xff0c;觉得好牛逼…

java异常 | 处理规范、全局异常、Error处理

文章目录 &#x1f683;异常类型&#x1f3a0;显示声明异常&#xff1a;①&#xff1a;try-catch②方法签名 &#x1f683;异常处理规范⚓️异常包装⚓️异常传递⚓️异常日志记录⚓️异常处理的最佳实践 &#x1f683;全局异常处理⛵️优点&#xff1a;⛵️代码示例&#xff1…

YOLOv7训练自定义数据集

使用YOLOv7做对比实验&#xff0c;需要重新部署一下YOLO环境&#xff0c;并将COCO格式数据集转换为YOLO格式 博主的COCO数据集是由WiderPerson数据集转换来的&#xff0c;并且做了一些处理。 环境 Ubuntu18.0 CUDA11.2 NVIDIA T4 项目部署 下载项目&#xff1a; git clone…

PureComponent和Component的区别和底层处理机制

PureComponent和Component都是React中的组件类&#xff0c;但它们在实现细节和使用上有些差别。 Component是React中定义组件的基类&#xff0c;它的shouldComponentUpdate方法默认返回true&#xff0c;也就是说&#xff0c;每次调用setState或forceUpdate方法都会引发组件重新…

算法提高-图论-单源最短路的综合应用

单源最短路的综合应用 单源最短路的综合应用AcWing 1135. 新年好AcWing 340. 通信线路AcWing 342. 道路与航线AcWing 341. 最优贸易 单源最短路的综合应用 AcWing 1135. 新年好 多次dijkstra求每个点到其它点的最短距离&#xff0c; 此时相当于建好了一张图&#xff0c;每个点…