智能指针之设计模式4

前面的文章介绍了使用工厂模式来封装智能指针对象的创建过程,下面介绍一下工厂类

enable_shared_from_this的实现方案。

4、模板方法模式

在前面的文章分析过,enable_shared_from_this<T>类是一个工厂基类,提供的工厂方法是shared_from_this(),而shared_ptr<T>就是一个具体的产品类,每一个shared_ptr<T>对象都需要一个具体的工厂类来创建它,这个具体的工厂类就是T,它是enable_shared_from_this<T>的派生类,同时又是模板参数类型。那么,C++标准库是怎么通过this指针来创建shared_ptr<T>对象的呢?

按照常规的做法,可以继承工厂基类然后由派生类来实现基类定义的工厂方法接口,然而当在一个对象内部通过this指针创建一个shared_ptr对象时,却不是一件容易的事情:

首先,要判断资源对象是否是在堆中,如果资源对象不是在堆中,那么使用shared_ptr<T>(this)创建的智能指针对象,在析构时就会发生异常。

其次,要确定资源对象是否已经被shared_ptr管理了,如果管理了,也不能直接使用shared_ptr<T>(this)创建的智能指针对象,否则会导致由两个不同的shared_ptr来管理this,最后会发生重复释放内存的错误。

再次,如果在资源对象内部使用shared_ptr<T>(this)创建了智能指针对象,但是用户在外部是不知道的,可能又使用shared_ptr<T>(obj_ptr)创建了智能指针对象,也会发生重复释放内存的错误。

如果这个创建过程,完全交给程序员来实现,需要仔细地约束各方面的代码实现,还得需要相关人员认真地评审,防止出现上面描述的错误,带来了极大的风险和不便。C++标准库的思路是交由代码来解决,把通过this指针创建shared_ptr对象的代码封装起来形成固定的模板套路,只把需要定制的部分交给派生类去实现,也就是模板方法模式,即在模板基类中把通过this指针创建shared_ptr对象的过程封装成“模板方法”,它执行过程中要使用具体派生类提供的“钩子函数",这样哪个类要想提供shared_from_this()功能,就继承模板基类并实现”钩子函数“就可以了。下面是GOF模板方法模式的结构图:
在这里插入图片描述

从结构图中可以看出,模板方法模式实际上就是面向对象编程中的继承和动态多态机制,也就是在基类中定义virtual函数接口,也就是“钩子方法”,在派生类中实现virtual函数,从而让基类中的“模板方法”能够通过动态绑定的方式,来调用派生类提供的virtual函数。

不过enable_shared_from_this<T>作为模板基类与上面的模板方法模式在形式上不同,它没有采用面向对象编程的多态机制,而是利使用了一种特殊的继承方式实现的,即CRTP。CRTP采用了C++的模板技术,虽然也是继承方式,但却是在基类中通过把自己强制转为派生类类型的方式来调用派生类实现的函数,这是二者最大的区别。模板方法模式是一个共有的基类,然后有多个同一族类的派生类,它们复用了基类中的“模板方法”,并重写了virtual函数,属于动态绑定;而CRTP方式是一个基类仅有一个派生类,这样完全可以把基类强制转换成派生类,调用的是派生类的非virtual成员函数,属于静态绑定机制。同样,因为CRTP派生类继承了基类,它复用了基类的“模板方法”,因此,外界也可以调用派生类的“模板方法”来实现具体的功能。

shared_from_this()创建shared_ptr对象的套路是,定义了一个weak_ptr数据成员,在创建shared_ptr对象时,就使用这个weak_ptr成员来创建,派生类显然是无法设置这个weak_ptr成员的,也就是说无法设计成提供一个“钩子函数”的形式,而weak_ptr类型和shared_ptr类型息息相关,因此enable_shared_from_this<T>就让shared_ptr<T>作为它的友元类,要求shared_ptr在创建对象时,同时初始化这个weak_ptr成员。这样,如果派生类T没有使用shared_ptr对象来管理自己的生存期,也就初始化不了这个weak_ptr成员,进而也就无法通过this创建出shared_ptr对象,而通过weak_ptr对象创建的shared_ptr对象和原来的shared_ptr对象共享T资源对象的所有权,也不会发生直接使用this指针创建了shared_ptr对象之后,导致由两个不同的shared_ptr来管理this的错误。

可见,在这个模板方法模式中,“钩子方法”不是一个具体函数,它是一个抽象的概念,或者说是一个“钩子数据”,在这里就是它的weak_ptr类型的数据成员,模板方法在实现套路化的逻辑时,需要使用weak_ptr对象数据成员来创建shared_ptr对象,这个weak_ptr对象是在创建shared_ptr对象时初始化的,因此要求创建它的派生类对象时,必须要被一个shared_ptr对象托管,这样shared_ptr在创建对象时,就会同时初始化这个weak_ptr成员。也就是说,派生类不需要提供具体的“钩子函数”,只要它在创建时候,同时创建一个shared_ptr对象来管理它的生存期,它自然而然地就为基类的“模板方法”提供了“钩子数据”。

这里,相当于把通过this指针创建shared_ptr对象的过程给封装成模板方法,谁要是想通过this指针创建shared_ptr对象就可以直接继承enable_shared_from_this类,它的模板方法自己实现了这个功能,派生类无需专门编写代码,为程序员编程带来了便利。下面是结构图:
在这里插入图片描述

enable_shared_from_this<resource>是工厂基类也是模板方法类,它的成员函数shared_from_this()是"模板方法",职责是使用资源对象resource的this指针来创建一个shared_ptr对象;资源对象resource类是它的一个公共派生类,是工厂派生类也是模板方法派生类,具体产品对象shared_ptr<resource>就是从resource中创建出来的,也就是一个产品对象通过“模板方法”创建了一个管理自己生存期的shared_ptr对象。示例代码如下:

class resource : public enable_shared_from_this<resource> {data_obj obj;public:void process() {auto sp = shared_from_this();...}... // 其它成员函数
};
...

只要继承了enable_shared_from_this<T>类,它就是一个模板方法派生类,就可以使用模板方法类的shared_from_this(),然而这只是“模板方法”函数,还得需要派生类自己提供“钩子方法”函数,也就是保证派生类创建的对象被shared_ptr托管,这样就可以根据模板方法来通过this指针创建shared_ptr的副本了。因此,resoure资源对象可以通过:

shared_ptr<resource> res1(new resource);
shared_ptr<resource> res2 = make_shared<resource>();
shared_ptr<resource> res3 = weak_ptr_obj.lock();
shared_ptr<resource> res4(weak_ptr_obj());
shared_ptr<resource> res5(unique_ptr_obj());

等这些方式来创建shared_ptr<resource>对象,可以把这些创建方式等同于“钩子方法”,派生类resource只要能够创建出shared_ptr<resource>对象,当在resource对象内部使用this指针调用“模板方法”shared_shared_this()时,就会按照固定的套路创建出一个shared_ptr<resource>对象。显然这样就简化了根据this创建shared_ptr<resource>对象的过程,程序也不需要作太多的考虑,只要保证resource对象是使用shared_ptr智能指针管理的就可以了。

总之,工厂方法shared_from_this()是使用模板方法模式实现的,而模板模式又是使用CRTP惯用法来实现的。

参考:
https://cloud.tencent.com/developer/article/2362395

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

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

相关文章

【Nova UI】十、打造组件库第一个组件-图标组件(下):从.svg 到 SVG Vue 组件的高效蜕变✨

序言 在组件库开发的精彩旅程中&#x1f680;&#xff0c;我们已经成功打造并完善了图标组件体系&#xff0c;赋予其强大的功能和丰富的表现力&#x1f389;。然而&#xff0c;随着业务版图的不断扩张&#x1f310;&#xff0c;手动逐个编写 SVG Vue 组件的传统方式&#xff0…

Golang | 倒排索引

文章目录 倒排索引的设计倒排索引v0版实现 倒排索引的设计 通用搜索引擎 v.s. 垂直搜索引擎&#xff1a; 通用搜索引擎&#xff1a;什么都可以搜索&#xff0c;更加智能化垂直搜索引擎&#xff1a;只能搜自家数据库里面的内容&#xff0c;一般都带着搜索条件&#xff0c;搜索一…

Windows 10 上运行 Ollama 时遇到 llama runner process has terminated: exit status 2

在 Windows 10 上运行 Ollama 时遇到 llama runner process has terminated: exit status 2 错误&#xff0c;可能是由多种原因引起的。以下是逐步解决方案&#xff1a; 1. 检查 Ollama 服务状态 按 Win R 输入 services.msc&#xff0c;找到 Ollama 服务&#xff0c;确保其状…

PCI 总线学习笔记(五)

PCI 总线学习系列&#xff0c;参考自 技术大牛博客&#xff1a; PCIe 扫盲系列博文连载目录篇 书籍&#xff1a;王齐老师的《PCI Express 体系结构导读》 下面的文章中加入了自己的一些理解和实际使用中遇到的一些场景&#xff0c;供日后查询和回忆使用 PCI 总线定义了两类配置…

Spring Cloud Alibaba VS Spring Cloud

​​Spring Cloud Alibaba 与 Spring Cloud 组件对比​ ​​服务发现与注册中心​ 功能​​​Spring Cloud​​​Spring Cloud Alibaba​对比说明​​核心组件​EurekaNacosNacos 支持动态配置管理、健康检查更灵活&#xff0c;且提供 DNS 服务发现能力。​​​​健康检查​​…

Java—— 常见API介绍 第五期

JDK8以后新增的时间相关类 Date类ZoneId&#xff1a;时区Instant&#xff1a;时间戳ZoneDateTime&#xff1a;带时区的时间 日期格式化类 SimpleDateFormat DateTimeFormatter&#xff1a;用于时间的格式化和解析 日历类 Calendar LocalDate&#xff1a;年、月、日LocalTime…

Java与Kotlin在Android开发中的全面对比分析

趋势很重要 语言发展背景与现状 Android操作系统自2008年正式发布以来&#xff0c;Java长期作为其主要的开发语言。这种选择源于Java语言的跨平台特性、成熟的生态系统以及广泛开发者基础。然而&#xff0c;随着移动开发需求的快速演变&#xff0c;Java在Android开发中逐渐暴…

第一部分:git基本操作

目录 1、git初识 1.1、存在的问题 1.2、版本控制器 1.3、git安装 1.3.1、CentOS平台 1.3.2、ubuntu平台 2、git基本操作 2.1、创建仓库 2.2、配置git 3、工作区、暂存区、版本库 4、基本操作 4.1、场景一 4.2、场景二 4.3、修改文件 5、版本回退 6、撤销修改 …

正则表达式与python使用

一、Python正则表达式基础 1. 导入模块 Python通过 re 模块实现正则表达式功能&#xff0c;需先导入模块&#xff1a; import re2. 核心语法 普通字符&#xff1a;直接匹配字面值&#xff08;如 a 匹配字符 a&#xff09;。元字符&#xff1a; \d&#xff1a;匹配数字&…

从FP32到BF16,再到混合精度的全景解析

笔者做过目标检测模型、超分模型以及扩散生成模型。其中最常使用的是单精度FP32、半精度FP16、BF16。 双精度"FP64"就不说了&#xff0c;不太会用到。 #1. 单精度、半精度和混合精度 单精度&#xff08;FP32&#xff09;、半精度&#xff08;FP16&#xff09;和混合…

Hot100方法及易错点总结2

本文旨在记录做hot100时遇到的问题及易错点 五、234.回文链表141.环形链表 六、142. 环形链表II21.合并两个有序链表2.两数相加19.删除链表的倒数第n个节点 七、24.两两交换链表中的节点25.K个一组翻转链表(坑点很多&#xff0c;必须多做几遍)138.随机链表的复制148.排序链表 N…

不在同一个局域网的远程桌面连接怎么设置?本地内网计算机让其他网络远程访问6种常用方法

远程桌面是一种重要的技术&#xff0c;它允许用户通过网络远程访问和控制另一台计算机的桌面界面。但是&#xff0c;当被控制端和控制端不在同一个局域网内时&#xff0c;就需要进行一些额外的配置。本文将详细介绍在不同局域网下设置远程桌面的步骤&#xff0c;以帮助读者顺利…

天机学堂day10作业,完善兑换优惠券功能

UserCouponServiceImpl /*** 兑换码兑换优惠券* param code*/TransactionalOverridepublic void exchangeCoupon(String code) {//1、校验code是否为空if (StringUtils.isBlank(code)) {throw new BadRequestException("非法参数&#xff01;");}//2、解析兑换码&…

JAVA工程师面试题(七)

1、递归实现1,1,2,3,5,8,….第30个数是多少&#xff1f; public static int Foo(int i) { if (i < 0) return 0; else if(i > 0 && i < 2) return 1; else return Foo(i -1) Foo(i - 2); }…

Qt基础009(HTTP编程和QJSON)

文章目录 软件开发网络架构BS架构/CS架构 HTTP基本概念QT的HTTP编程JSON数据概述QT生成JSON数据QT解析JSON数据 软件开发网络架构 BS架构/CS架构 ​ 在计算机网络和软件开发中&#xff0c;CS架构&#xff08;Client-Server Architecture&#xff0c;客户端-服务器架构&#x…

高精度电流检测革命:同轴分流器的创新应用与技术演进

一、精密测量原理与结构创新 基于电磁场分布重构技术的新型同轴分流器&#xff0c;突破了传统电流测量的物理限制。该器件采用三维环形电阻矩阵结构&#xff0c;通过多层级导电环的精密排列&#xff0c;实现了电流路径的涡流自补偿。区别于常规分流器的平板式设计&#xff0c;其…

【使用层次序列构建二叉树(数据结构C)】

使用层次序列构建二叉树&#xff08;C语言实现&#xff09; 在数据结构学习过程中&#xff0c;二叉树的构建方式通常有递归建树&#xff08;前序/中序&#xff09;和层次建树&#xff08;广度优先&#xff09;两种。本文将介绍一种基于辅助队列实现的层次建树方法&#xff0c;并…

设置Rocky Linux盒盖不休眠的3个简单步骤

在 Rocky linux&#xff08;和其他基于 RHEL 的发行版&#xff09;中&#xff0c;当你关闭笔记本电脑的盖子时&#xff0c;默认行为通常是使系统休眠。如果你想更改这一行为&#xff0c;例如&#xff0c;使系统在关闭盖子时只是锁定&#xff0c;你可以按照以下步骤操作&#xf…

WPF的发展历程

文章目录 WPF的发展历程引言起源与背景&#xff08;2001-2006&#xff09;从Avalon到WPF设计目标与创新理念 WPF核心技术特点与架构基础架构与渲染模型关键技术特点MVVM架构模式 WPF在现代Windows开发中的地位与前景当前市场定位与其他微软UI技术的关系未来发展前景 社区贡献与…

【器件专题1——IGBT第1讲】IGBT:电力电子领域的 “万能开关”,如何撑起新能源时代?

一、IGBT 是什么&#xff1f;重新认识这个 “低调的电力心脏” 你可能没听过 IGBT&#xff0c;但一定用过它驱动的设备&#xff1a;家里的变频空调、路上的电动汽车、屋顶的光伏逆变器&#xff0c;甚至高铁和电网的核心部件里&#xff0c;都藏着这个 “电力电子开关的瑞士军刀”…