智能指针(2)

news2024/10/7 17:27:20

智能指针(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一起释放,但当内存很大时等待其结束释放便时很效率很低的。(一次申请的堆内存智能一次释放完,不能分多次释放)。

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

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

相关文章

chatgpt赋能python:Python如何判断输入的字符——基础教程与实例

Python如何判断输入的字符——基础教程与实例 时至今日&#xff0c;互联网已经成为人们获取信息的重要途径&#xff0c;而搜索引擎优化&#xff08;SEO&#xff09;则是网站重要的推广手段之一。而Python作为一种高级编程语言&#xff0c;在实现SEO时也有很大的优势&#xff0…

chatgpt赋能python:Python如何进行升序和降序排列

Python如何进行升序和降序排列 Python是一种非常流行的编程语言&#xff0c;由于其在数据科学、机器学习和人工智能等领域的强大表现&#xff0c;越来越多的人开始学习和使用Python。在Python中&#xff0c;排序是一项非常常见的操作。在这篇文章中&#xff0c;我将向您介绍如…

stable diffusion webui 登录接口(login)api接口调用(使用C#)

唠嗑 本次将跟读者讲一下如何通过C#请求sd webui api【login】接口&#xff0c;如果读者觉得文章有用&#xff0c;请给【点个赞】吧&#xff0c;有问题可以评论区提问。 实战 1.配置api启用参数 启动webui时&#xff0c;需加上【–api】 、【–api-auth 账号:密码】 和【–…

chatgpt赋能python:Python字符类型判断:如何判断字符是字母或数字

Python字符类型判断&#xff1a;如何判断字符是字母或数字 在Python编程中&#xff0c;经常需要判断一个字符是字母还是数字。本文将介绍如何在Python中判断字符类型&#xff0c;并给出几个示例。 判断字符类型的方法 在Python中&#xff0c;可以使用以下方法来判断字符类型…

chatgpt赋能python:Python中如何删除变量中的字符

Python中如何删除变量中的字符 在Python编程中&#xff0c;我们有时需要清除变量中的字符。删除字符可以是去掉字符串中的某些字符&#xff0c;也可以是从列表或元组中删除某些元素。本文将介绍Python中如何删除变量中的字符。 删除字符串中的字符 Python使用字符串的切片操…

网络安全入门学习第十五课——PHP基础

文章目录 一、WEB技术1、什么是web2、B/S架构3、C/S架构 二、PHP概述1、PHP是什么2、PHP受欢迎的原因3、基于MVC模式的PHP框架4、常用编译工具5、PHP环境搭建6、开发工具 三、PHP基本语法格式1、标记2、输出语句3、注释4、标识符 四、数据与运算1、常量1.1、常量定义1.2、预定义…

前端vue实现页面加水印文字 单个页面所有页面加水印 水印颜色

前端vue实现页面加水印文字, 可以实现系统所有页面加水印,也可以单个页面加水印, 可更改水印颜色, 下载完整代码请访问uni-app插件市场地址: https://ext.dcloud.net.cn/plugin?id12889 效果图如下: #### 使用方法 使用方法 /* 给系统所有页面加水印*/ // 第一个参数:水印…

Shell脚本函数简介及运用

目录 一、函数的作用 二、定义函数 三、调用函数 1.在脚本中调用函数 2.在函数中调用函数 四、函数传参 五、函数的返回值 六、函数的递归 七、函数及其变量的作用范围 八、外部脚本调用函数 一、函数的作用 语句块定义成函数约等于别名&#xff0c;定义函数&#xf…

【云原生网关】Apache ShenYu 使用详解

目录 一、前言 二、Apache ShenYu 介绍 2.1 为什么叫ShenYu 2.2 ShenYu特点 2.3 ShenYu架构图 2.4 shenyu数据同步原理 2.4.1 Zookeeper数据同步原理 三、Apache ShenYu 安装部署 3.1 部署流程 3.1.1 创建 Docker Network 3.1.2 拉取Apache ShenYu Admin镜像 3.1.3…

C语言之函数栈帧的创建与销毁(2)

上一篇博客我们讲到了函数栈帧的创建与销毁&#xff08;1&#xff09;今天我们来讲解Add函数的函数栈帧相关知识 在开始本章博客之前&#xff0c;大家可以把上一篇博客的主要内容仔细复习一下 看图 第一个mov&#xff1a;把b的值放到eax里面去 第二个mov&#xff1a;把a的…

【python】【excel】用excel中指定单元格的内容去替换一个文本中指定的字符

1 使用背景 理正的.spw文件是文本格式&#xff0c;类似于该软件的前处理&#xff0c;相关参数字段可通过文本替换&#xff0c;快速修改参数。 后续用途可用在&#xff1a;用EXCEL整理数据&#xff0c;通过修改文本批量获取多个截面参数的spw文件 2 ExcelSheet-shift-textstr…

macOS中解决matplotlib中文乱码

现象 图表上中文变方框&#xff0c;日志中报错如下&#xff1a; findfont: Generic family sans-serif not found because none of the following families were found: 解决办法 下载字体 http://xiazaiziti.com/210356.html 查询字体保存路径 查看配置文件路径 import…

01- 数据类型(C语言)

一 变量和常量 1.1 标识符 1、在我们所写的“第一个C程序”中出现了很多的标识符&#xff0c;例如include、main、printf、return。标识符是⽤来标识变量、函数&#xff0c;或任何其他⽤户⾃定义项⽬的名称。 2、标识符的约束规范&#xff1a; 只能包含数字、字母和下划线不…

chatgpt赋能python:Python怎么删除列表

Python怎么删除列表 什么是Python列表&#xff1f; 在Python中&#xff0c;列表是一个可变的序列&#xff0c;它可以包含不同类型的数据。列表可以使用中括号 [] 来定义&#xff0c;每个元素之间用逗号分隔。列表中的元素可以通过其下标进行访问&#xff0c;下标从0开始。 P…

Skywalking基础使用

Skywalking基础使用 agent的使用Linux下Tomcat7和8中使用Spring Boot中使用RocketBot的使用 agent的使用 agent探针可以让我们不修改代码的情况下&#xff0c;对java应用上使用到的组件进行动态监控&#xff0c;获取运行数据发送到OAP上进行统计和存储。agent探针在java中是使…

C语言之数组初级(5-8)

目录 1. 一维数组的创建和初始化 2. 一维数组的使用 3. 一维数组在内存中的存储 4. 二维数组的创建和初始化 5. 二维数组的使用 6. 二维数组在内存中的存储 7. 数组越界 8. 数组作为函数参数 二维数组的画图讲解 例如现在我要找第二行第三列这个元素&#xff0c;下面是…

放大电路与频率特征(期末模电速成)

目录 1、放大电路基础 2、放大电路三种组态 3、放大电路分析&#xff08;必考&#xff09; 4、多级放大电路 5、差动放大电路 6、频率特征 1、放大电路基础 晶体管输出特性曲线中的线性区域是指 放大区 &#xff0c;iC βiB NPN 型静态工作点过高&#xff0c;容易产生 饱…

Vue 中的表格操作

Vue 中的表格操作 在 Web 开发中&#xff0c;表格是非常常见的元素之一。在 Vue 中&#xff0c;我们可以使用一些组件和插件来实现表格的操作。在本文中&#xff0c;我们将介绍 Vue 中的表格操作的基本原理和用法&#xff0c;并给出一些实例代码来帮助读者更好地理解。 表格…

Scrapy爬取数据,使用Django+PyEcharts实现可视化大屏

项目介绍 使用Scrapy进行数据爬取&#xff0c;MySQL存储数据&#xff0c;Django写后端服务&#xff0c;PyEcharts制作可视化图表&#xff0c;效果如下。 项目下载地址&#xff1a;Scrapy爬取数据&#xff0c;并使用Django框架PyEcharts实现可视化大屏 发现每个模块都有详情页…

Django---------创建、运行

目录 1.安装django 2.pycharm&#xff08;专业版&#xff09;创建项目 3.默认项目的文件介绍 4.App的创建和说明 5. 启动运行django 1.确保app已注册[settings.py] 2. 编写URL和视图函数对应关系[url.py] 3.编写视图函数[views.py] 4.启动django项目 1.安装django pip…