奇异递归模板模式应用6-类模板enable_shared_from_this

异步编程中存在一种场景,需要在类中将该类的对象注册到某个回调类或函数中,不能简单地将this传递给回调类中,很可能因为回调时该对象不存在而导致野指针访问(也有可能在析构函数解注册时被回调,造成对象不完整)。若通过智能指针管理对象,也不能直接将this构造成shared_ptr,因为无法通过裸指针获得引用计数块信息(而侵入式智能指针的引用计数块和对象内存连续,可以简单地通过偏移引用计数块大小得到),强行构造会导致对象内存多次释放。《C++20高级编程》

书中用例

struct Obj {
	void submit() {
		// 内存风险,无法通过裸指针获得引用计数块信息
		executor.submit([this]{ this->onComplete(); });
	}
}
// 奇异递归模板模式
struct Obj : enable_shared_from_this<Obj> {
	void submit() {
		// weak_from_this()获得this指针的弱指针
		executor.submit([obj = weak_from_this()]{
			// lock提升至共享指针
			if (auto spObj = obj.lock()) {
				spObj->onComplete();
			}
		});
	}
}

下面演示一下书中说的无法通过裸指针获得引用计数块信息的含义
1、错误示例:

class A
{
public:
	A() { std::cout << "A的构造函数" << std::endl; }
	~A() { std::cout << "A的析构函数" << std::endl; }

	std::shared_ptr<A> getSharedPtr() { return std::shared_ptr<A>(this); }
};

int main()
{
	{
		// 演示shared_ptr的正确用法(推荐使用std::make_shared()构造,能够使得引用计数块和对象在内存上连续)
		std::cout << "代码块【1】开始" << std::endl;
		A *pa = new A();
		std::shared_ptr<A> sp1(pa);
		std::cout << "A的引用计数,sp1.use_count() = " << sp1.use_count() << std::endl;
		std::shared_ptr<A> sp2(sp1);
		std::cout << "A的引用计数:sp2.use_count() = " << sp2.use_count() << std::endl;
		std::cout << "代码块【1】结束" << std::endl;
	}

	{
		std::cout << "代码块【2】开始" << std::endl;
		A *pa = new A();
		std::shared_ptr<A> sp1(pa);
		std::cout << "A的引用计数,sp1.use_count() = " << sp1.use_count() << std::endl;
		std::shared_ptr<A> sp2(pa);
		std::cout << "A的引用计数:sp2.use_count() = " << sp2.use_count() << std::endl;
		std::shared_ptr<A> sp3(pa->getSharedPtr());
		std::cout << "A的引用计数:sp3.use_count() = " << sp3.use_count() << std::endl;
		std::cout << "代码块【2】结束" << std::endl;
	}
	system("pause");
	return 0;
}

在这里插入图片描述
代码块【1】中使用了共享指针的拷贝操作,引用计数是正确的。而代码块【2】中的三个共享指针都是用裸指针pa初始化的,那么得到的三个共享指针sp1、sp2和sp3各不相干,各自维护着自己的引用计数,因而引用计数都是1。也就是书中说的“无法通过裸指针获得引用计数块信息”。
由于引用计数错误,那么在代码块【2】结束的时候,这三个共享指针sp1、sp2和sp3都将释放内存,就会导致内存要被释放3次,这显然会让程序崩溃。因此在第二次释放内存时,程序崩溃,所以图中打印信息只显示2行“A的析构函数”。
2、正确示例

class A : public std::enable_shared_from_this<A>
{
public:
	A() { std::cout << "A的构造函数" << std::endl; }
	~A() { std::cout << "A的析构函数" << std::endl; }

	std::shared_ptr<A> getSharedPtr() { return weak_from_this().lock(); }
};

int main()
{
	{
		std::cout << "代码块【3】开始" << std::endl;
		A *pa = new A();
		std::shared_ptr<A> sp1(pa);
		std::cout << "A的引用计数,sp1.use_count() = " << sp1.use_count() << std::endl;
		std::shared_ptr<A> sp2(sp1); // 修改代码块【2】的裸指针pa为共享指针sp1
		std::cout << "A的引用计数:sp2.use_count() = " << sp2.use_count() << std::endl;
		std::shared_ptr<A> sp3(pa->getSharedPtr());
		std::cout << "A的引用计数:sp3.use_count() = " << sp3.use_count() << std::endl;
		std::cout << "代码块【3】结束" << std::endl;
	}
	
	system("pause");
	return 0;
}

在这里插入图片描述

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

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

相关文章

【C语言基础】:操作符详解(一)

文章目录 操作符详解1. 操作符的分类2. 二进制和进制转换2.1 什么是二进制、八进制、十进制、十六进制2.1.1 二进制和进制转换2.1.2 二进制转十进制2.2.3 二进制转八进制2.2.4 二进制转十六进制 3. 源码、反码、补码4. 移位操作符4.1 左移操作符4.2 右移操作符 5. 位操作符&…

IT廉连看——C语言——函数

IT廉连看——C语言——函数 一、函数是什么&#xff1f; 数学中我们常见到函数的概念。但是你了解C语言中的函数吗&#xff1f; 维基百科中对函数的定义&#xff1a;子程序 在计算机科学中&#xff0c;子程序&#xff08;英语&#xff1a;Subroutine, procedure, function, …

【Java】java异常处理机制(实验五)

目录 一、实验目的 二、实验内容 三、实验小结 一、实验目的 1、理解java的异常处理机制 2、掌握try catch结构和thow和thows关键字的用法 二、实验内容 1、编写一个程序&#xff0c;输入某个班某门课程成绩&#xff0c;统计及格人数、不及格人数及课程平均分。设计一个异…

H12-821_59

59.R1、R2、R3、R4运行IS-IS,它们接口的DIS Priority如图所示,假如设备同时启动,则()被选举为D1S.(请填写设备名称、例如R1) 答案&#xff1a;R4 注释&#xff1a; IS-IS中DIS的选举支持抢占。 假设题目说R4最后启动&#xff0c;问谁被选举为DIS&#xff0c;答案仍然是R4。

【嵌入式实践】【芝麻】【设计篇-1】从0到1给电动车添加指纹锁:项目设计思路

0. 前言 该项目是基于stm32F103和指纹模块做了一个通过指纹锁控制电动车的小工具。支持添加指纹、删除指纹&#xff0c;电动车进入P档等待时计时&#xff0c;计时超过5min则自动锁车&#xff0c;计时过程中按刹车可中断P档状态&#xff0c;同时中断锁车计时。改项目我称之为“芝…

NVIDIA Workbench 安装使用图文教程

NVIDIA Workbench 安装使用教程 文章目录 NVIDIA Workbench 安装使用教程1.安装1.1 下载软件1.2 安装软件 2.使用NVIDIA Workbench2.1 创建一个新项目 3.额外提示3.1 当我们没有停止直接关闭或者直接重启电脑后, 再打开我们已经创立的项目的时候可能会出现创建失败等错误信息.3…

Java核心-核心类与API(3)

话接上回&#xff0c;继续核心类与API的学习&#xff0c;这次介绍一下枚举类以及与系统、交互有关的类&#xff0c;需要了解并能使用即可。 一、枚举类 1、概述 枚举也称穷举&#xff0c;简单理解就是把所有可能一一列举出来&#xff08;穷尽所有可能&#xff09;。枚举是一…

申请攻读博士学位研究生相关模板资料(包括专家推荐信、学术简历、研究计划及范文、回复导师邮件)

申请攻读博士学位研究生相关模板资料&#xff08;包括专家推荐信、学术简历、研究计划及范文、回复导师邮件&#xff09; 博士是对攻读博士学位的研究生的称呼&#xff0c;同样也可用来称呼已获得博士学位的人员。 主要通过拥有博士点的普通高等学校和拥有博士研究生培养资格…

[SUCTF 2019]EasySQL1 题目分析与详解

一、题目介绍 1、题目来源&#xff1a; BUUCTF网站&#xff0c;网址&#xff1a;https://buuoj.cn/challenges 2、题目描述&#xff1a; 通过以上信息&#xff0c;拿到flag。 二、解题思路 首先打开靶机&#xff0c;尝试输入1查看回显&#xff0c;回显如图所示&#xff1a;…

图像读取裁剪与人脸识别

图像读取 Image read ⇒ \Rightarrow ⇒ torchvision.datasets from torchvision import datasets dataset datasets.ImageFolder(data_dir, transformtransforms.Resize((512, 512)))Return value illustration dataset[0][0]是PIL.Image objects&#xff0c;这利用IPyth…

数据结构-列表LinkedList

一,链表的简单的认识. 数组,栈,队列是线性数据结构,但都算不上是动态数据结构,底层都是依托静态数组,但是链表是确实真正意义上的动态数组. 为什么要学习链表? 1,链表时最简单的动态数据结构 2,掌握链表有助于学习更复杂的数据结构,例如,二叉树,trie. 3,学习链表有助于更深入…

LeetCode 1038.从二叉搜索树到更大和树

给定一个二叉搜索树 root (BST)&#xff0c;请将它的每个节点的值替换成树中大于或者等于该节点值的所有节点值之和。 提醒一下&#xff0c; 二叉搜索树 满足下列约束条件&#xff1a; 节点的左子树仅包含键 小于 节点键的节点。 节点的右子树仅包含键 大于 节点键的节点。 左…

js逆向-2

#md5加密&#xff0c;某宝案例演示。 #免责声明:本文仅供学习使用&#xff0c;请勿用于其他违法行为(╥ω╥)

软件性能测试和功能测试有何联系和区别?第三方软件检测机构简析

软件性能测试和功能测试是软件开发过程中非常重要的两个环节。从根本上说&#xff0c;它们都是为了保证软件质量和可靠性&#xff0c;但它们的目标和方法却有所不同。 软件性能测试是评估软件在特定负载下的性能表现&#xff0c;包括响应时间、吞吐量、并发能力等指标。它通过…

MySQL 学习记录 2

原文&#xff1a;https://blog.iyatt.com/?p13818 13 存储引擎 查看一下前面创建的一张表的创建语句&#xff0c;当时并没有显式指定引擎&#xff0c;MySQL 自动指定的 InnoDB&#xff0c;即默认引擎是这个。 创建表的时候要显式指定引擎可以参考这个语句 查看当前 MySQL …

如何正确使用Postman变量?又该如何灵活设置变量?

引言 Postman变量可以帮助你快速生成测试数据、模拟不同的场景和环境。 但是&#xff0c;如何正确使用Postman变量&#xff1f;又该如何灵活设置变量&#xff1f;这些问题不用担心&#xff0c;接着往下看吧&#xff01; 理解变量 为什么要使用变量&#xff1f; 如果在多个…

探索Java11新世界:JDK 11新特性详解

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

设计模式篇---观察者模式

文章目录 概念结构实例总结 概念 观察者模式&#xff1a;定义对象之间的一种一对多的依赖关系&#xff0c;使得每当一个对象状态发生改变时&#xff0c;其他相关依赖对象都得到通知并被自动更新。 观察者模式是使用频率较高的一个模式&#xff0c;它建立了对象与对象之间的依赖…

一文读懂列表解析、字典解析、集合解析

一、所谓解析/解析式&#xff0c;也称为推导/推导式&#xff0c;对应英语单词为comprehension&#xff0c;是Python的一种独有特性。解析就是从一个数据序列构建另一个新的数据序列的结构体&#xff0c;其本质是使用一个可迭代对象&#xff0c;按一定规则通过表达式、函数等运算…

Git的基本操作和原理

目录 写在前面的话 为什么要有Git&#xff08;git初识&#xff09;&#xff1f; Git安装(Centos为例) Git基本操作 创建Git本地仓库 Git配置 认识工作区、暂存区、版本库 概念认识 添加文件 查看.git文件 修改文件 版本回退 撤销修改 情况一&#xff1a;…
最新文章