【模板的特殊继承关系】 奇异的递归模板模式
一、奇异的递归模板模式范例
奇异的递归模板模式 ( C u r i o u s l y R e c u r r i n g T e m p l a t e P a t t e r n ) (Curiously \ Recurring \ Template \ Pattern) (Curiously Recurring Template Pattern)不是一种新技术,而是一种模板编程中使用的编程手法:把派生类作为基类的模板参数。
下面是一个简单范例:
//基类
template<typename T>
class Base {//...
};//派生类1
class Derived1 : public Base<Derived1> { //派生类继承基类的派生类参数的泛型
public:void myfunc() {std::cout << "Derived1::myfunc()执行了!\n";}};//派生类2(模板)
template<typename T>
class Derived2 : public Base<Derived2<T>> {};
这样的继承方式在模板与泛型编程中很常见。
以下介绍一下基本的几个用法。
二、在基类中使用派生类对象
一般情况下,我们可以通过 s t d : : s t a t i c _ c a s t < T > std::static\_cast<T> std::static_cast<T>静态类型转换将基类对象转换为派生类对象,从而在基类对象内使用派生类对象的接口。
如下代码所示:
template<typename T>
class Base {private:Base() {}friend T; //只有在类内实例化Base<T>的T是友元public:void asDerived() {T& derived = static_cast<T&>(*this); //将基类转为派生类derived.myfunc();}
};//派生类1
class Derived1 : public Base<Derived1> { //派生类继承基类的派生类参数的泛型
public:void myfunc() {std::cout << "Derived1::myfunc()执行了!\n";}};
为了防止继承时出错,我们在基类内增加一个友元类 T T T,只对相同派生类的类模板开放构造函数。
使用友元可以避免下面的错误:

相应的友元模板知识点可以参考:友元类模板,这里就不赘述了。
注意这里的写法:

当然,这个派生类也可以是一个模板:
//派生类2(模板)
template<typename T>
class Derived2 : public Base<Derived2<T>> {
public:void myfunc() {std::cout << "Derived2<T>::myfunc()执行了!\n";}
};
同时,我们在基类增加友元声明:
template<typename U>
friend class Derived2; // 允许所有 Derived2 的实例成为友元
这样我们实例化出的派生类都支持基类的私有访问了,调用下方测试代码:
void Test1() {Derived1 myd1;myd1.asDerived();Derived2<int> myd2;myd2.asDerived();
}
这样的运行结果是,我们在基类调用了派生类的方法,也就是可以看作是派生类为基类提供了接口,这与一般的继承调用不同(基类给派生类提供接口)
运行结果如下:

三、基于减少派生类代码量的考虑
通常,如果我们需要在派生类内增加功能,我们需要不断的补充代码,而这里的做法是,我们把一部分代码放入基类实现,让派生类内的代码相对简洁一些。
下面是一个例子:
//基于减少派生类中代码量的考虑template<typename T>
struct shape {//友元实现运算符重载friend bool operator==(const shape<T>& obj1, const shape<T>& obj2) {const T& objtmp1 = static_cast<const T&>(obj1);const T& objtmp2 = static_cast<const T&>(obj2);if (!(objtmp1 < objtmp2) && !(objtmp2 < objtmp1)) return true;return false;}
};struct square :public shape<square> {int sidelength;square(int length) :sidelength(length) {}};//全局实现运算符重载
bool operator<(square const& obj1, square const& obj2) {return obj1.sidelength < obj2.sidelength;
}
我们需要在 s q u a r e square square派生类里面增加一个运算符重载,我们可以把这一个操作放入基类中来实现,或者放入全局来实现。
这里详细说一下在基类实现:
1.我们需要将运算符设置为友元的,这样我们就能在派生类内访问到基类的运算符了。这样设置的友元是全局的,这在之前的友元章节有详细说明。
2.然后我们需要使用 s t d : : s t a t i c _ c a s t < T > std::static\_cast<T> std::static_cast<T>对传入的基类对象转为相应的派生类对象,最后进行比较即可。
调用测试代码:
void Test2() {square obj1(15), obj2(20), obj3(20);if (obj1 == obj2) {std::cout << "obj1 和obj2相等\n";}else {std::cout << "obj1 和obj2不相等\n";}if (obj2 == obj3) {std::cout << "obj2 和obj3相等\n";}else {std::cout << "obj2 和obj3不相等\n";}
这样,我们在派生类中无需添加任何代码,而将代码加入基类或全局。

三、基类调用派生的接口与多态的体现
就像上面说的,这样的奇异递归模板模式可以让基类调用派生类的接口。
下面我们看看具体的实现:
//基类
template<typename T>
class Human {
private:Human() {}friend T;public:T& toChild() {return static_cast<T&>(*this); //基类转派生类}void parenteat() {toChild().eat();}};//派生类1
class Men :public Human<Men> {
public:void eat() {std::cout << "男人喜欢吃面食!\n";}
};//派生类2
class Women :public Human<Women> {
public:void eat() {std::cout << "女人喜欢吃米饭!\n";}
};
在基类中,核心代码如下:

首先是转为派生类,然后调用派生类接口
Men mymen;
Women mywomen;//派生类给基类提供接口
mymen.parenteat();
mywomen.parenteat();
运行后我们得到:

这里实现了让基类调用我们派生类的接口,或者说是让派生类给基类提供接口。
我们也能在此基础上,添加一个函数模板:
template<typename T>
void myHumanFuncTest(Human<T>& tmpobj) {tmpobj.toChild().eat();
}
调用:
Men mymen;
Women mywomen;
myHumanFuncTest(mymen);
myHumanFuncTest(mywomen);
得到相同的结果:

实际上,可以发现,这是多态的体现,也是前面我们说过的静态多态。
这样的编程方法可以让我们更灵活:比如当 e a t ( ) eat() eat()函数是一个静态成员函数或者函数模板的时候,我们就无法使用虚函数进行动态多态,因此就必须使用奇异的递归模板模式了。
相关文章:
【模板的特殊继承关系】 奇异的递归模板模式
一、奇异的递归模板模式范例 奇异的递归模板模式 ( C u r i o u s l y R e c u r r i n g T e m p l a t e P a t t e r n ) (Curiously \ Recurring \ Template \ Pattern) (Curiously Recurring Template Pattern)不是一种新技术,而是一种模板编程中使用的编程手…...
SAP B1 单据页面自定义 - 用户界面编辑字段
背景 接《SAP B1 基础实操 - 用户定义字段 (UDF)》,在设置完自定义字段后,如下图,通过打开【用户定义字段】可打开表单右侧的自定义字段页。然而再开打一页附加页面操作繁复,若是客户常用的定义字段,也可以把这些用户…...
MinIO【部署 02】Linux集群版本及Windows单机版、单机多目录版、分布式版(cmd启动脚本及winsw脚本分享)
Linux集群版及Windows单机版分布式版 1.Linux集群版1.1 安装启动停止1.2 将MinIO添加到服务 2.Windows2.1 官网安装2.2 本地测试2.2.1 cmd启动脚本2.2.2 winsw脚本 3.总结 1.Linux集群版 官网下载地址 https://min.io/download#/linux; 官网安装文档 https://min.i…...
手握18个大厂offer,我在大模型风口起飞
前言 在“金三银四”这一招聘旺季中,社交媒体上满是分享 offer 信息的“求助帖”。这些帖子通常只公布公司名称与薪资区间,而将具体岗位模糊化,以此作为判断岗位是否值得入职的衡量标准。 2024 年毕业的 985 硕士白丁(化名&…...
邦芒忠告:办公室聊天应避开的四个话题
职场人生风云变幻,害人之心不可有,防人之心不可无。千万别把同事当知己,无话不谈,把自己的私域圈起来当成办公室话题的禁区,轻易不让人涉足,其实是非常明智的一招,是竞争压力下的自我保护。 话题…...
交易型开放式指数基金(ETF)
交易型开放式指数基金(Exchange Traded Fund,简称 ETF)是一种投资工具,以下是用通俗易懂的语言对其进行的讲解: 一、基本概念 想象 ETF 是一个大篮子,里面装着很多不同的东西。在金融市场里,这…...
opencv将灰度图转为彩色图片
文章目录 背景灰度图优势opencv读取灰度图彩色转灰度算法需求 方法测试代码 背景 在图像处理中通常需要将图片转为灰度图 灰度图,也称为灰度图像或黑白图像,是一种只包含亮度信息而不包含颜色信息的图像。在灰度图中,每个像素的亮度级别通常…...
判断PDF与图片是否可以预览
一、判断图片是否可以预览 在JavaScript中,可以使用Image对象来判断一个图片URL是否可以访问。如果图片可以被加载,那么load事件会被触发;如果图片无法访问,error事件会被触发。 function checkImageAccessibility(url, callbac…...
多线程与并发区别
在Java中,多线程与并发是两个既相关又有所区别的概念。我们可以这样来理解它们: 多线程(Multi-threading): 多线程是指程序能够同时执行多个线程。每个线程都是一个独立的执行流,它们共享程序的内存空间&a…...
这个桌面日历真不错 笔记 提醒 生日记录 打卡 翻译都有 真的太方便了!
这个桌面日历真不错 笔记 提醒 生日记录 打卡 翻译都有 真的太方便了!日历产品非常的多,如何选择一个合适自己的桌面日历,这个很重要,今天小编给大家介绍这个芝麻日历,一起看下它有些什么功能,是不是你需要…...
多模态大语言模型综述(中)-算法实用指南
本文是Multimodal Large Language Models: A Survey的译文之算法实用指南部分。 上:摘要、概念与技术要点实用指南中:算法实用指南(本文)下: 任务的实用指南(应用)、挑战等 原始信息 标题: Multimodal Large Language Models: A Survey译文: 多模态大…...
Qt | ubuntu20.04安装Qt6.5.3并创建一个example完整教程(涉及诸多开发细节,商用慎重)
点击上方"蓝字"关注我们 01、下载 >>> 下载Qt在线安装包 这里采用镜像地址进行下载,避免网络过慢。 镜像地址:http://mirrors.ustc.edu.cn/qtproject/archive/online_installers/4.5/ 选择最新版本下载,如截至目前最新版本为qt-unified-linux-x64-4.5.2…...
苏州科技大学、和数联合获得国家知识产权局颁发的3项发明专利证书
近日,基于“苏州科技大学-和数智能软件区块链技术工程实验室”的研究成果,国家知识产权局正式授权了苏州科技大学、苏州和数区块链应用研究院联合申报的3项发明专利证书。 分别为: 一种基于双账本的物联网数据存储与共享方法 一种面向物联网…...
CleanMyMac X2024破解版mac电脑清理工具
今天,我要跟大家分享一个让我彻底告别电脑卡顿的秘密武器——CleanMyMac X。这不仅仅是一款普通的清理工具,它是你电脑的私人健身教练,让电脑焕发青春活力! CleanMyMac直装官方版下载地址: http://wm.makeding.com/i…...
微软数据库的SQL注入漏洞解析——Microsoft Access、SQLServer与SQL注入防御
说明:本文仅是用于学习分析自己搭建的SQL漏洞内容和原理,请勿用在非法途径上,违者后果自负,与笔者无关;本文开始前请认真详细学习《中华人民共和国网络安全法》及其相关法规内容【学法时习之丨网络安全在身边一图了解网络安全法_中央网络安全和信息化委员会办公室】 。…...
无人机之处理器篇
无人机的处理器是无人机系统的核心部件之一,它负责控制无人机的飞行、数据处理、任务执行等多个关键功能。以下是对无人机处理器的详细解析: 一、处理器类型 无人机中使用的处理器主要包括以下几种类型: CPU处理器:CPU是无人机的…...
828华为云征文 | 华为云Flexus X实例上实现Docker容器的实时监控与可视化分析
前言 华为云Flexus X,以顶尖算力与智能调度,引领Docker容器管理新风尚。828企业上云节之际,Flexus X携手前沿技术,实现容器运行的实时监控与数据可视化,让管理变得直观高效。无论是性能瓶颈的精准定位,还是…...
缓存预热/雪崩/穿透/击穿
1. 缓存预热 预先将MySQL中的数据同步至Redis的过程 2. 缓存雪崩 Redis主机出现故障,或有大量的key同时过期大面积失效导致Redis不可用 Redis中key设置为永不过期,或者过期时间错开Redis缓存集群实现高可用多缓存结合预防雪崩服务降级 3. 缓存穿透 …...
C/C++:优选算法
一、双指针 1.1移动零 链接:283. 移动零 - 力扣(LeetCode) 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。请注意 ,必须在不复制数组的情况下原地对数组进行操…...
用于大数据分析的数据存储格式:Parquet、Avro 和 ORC 的性能和成本影响
高效的数据处理对于依赖大数据分析做出明智决策的企业和组织至关重要。显著影响数据处理性能的一个关键因素是数据的存储格式。本文探讨了不同存储格式(特别是 Parquet、Avro 和 ORC)对 Google Cloud Platform (GCP) 上大数据环境…...
【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...
从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障
关键领域软件测试的"安全密码":Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力,从金融交易到交通管控,这些关乎国计民生的关键领域…...
Chrome 浏览器前端与客户端双向通信实战
Chrome 前端(即页面 JS / Web UI)与客户端(C 后端)的交互机制,是 Chromium 架构中非常核心的一环。下面我将按常见场景,从通道、流程、技术栈几个角度做一套完整的分析,特别适合你这种在分析和改…...
全面解析数据库:从基础概念到前沿应用
在数字化时代,数据已成为企业和社会发展的核心资产,而数据库作为存储、管理和处理数据的关键工具,在各个领域发挥着举足轻重的作用。从电商平台的商品信息管理,到社交网络的用户数据存储,再到金融行业的交易记录处理&a…...
6️⃣Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙
Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙 一、前言:离区块链还有多远? 区块链听起来可能遥不可及,似乎是只有密码学专家和资深工程师才能涉足的领域。但事实上,构建一个区块链的核心并不复杂,尤其当你已经掌握了一门系统编程语言,比如 Go。 要真正理解区…...
Django RBAC项目后端实战 - 03 DRF权限控制实现
项目背景 在上一篇文章中,我们完成了JWT认证系统的集成。本篇文章将实现基于Redis的RBAC权限控制系统,为系统提供细粒度的权限控制。 开发目标 实现基于Redis的权限缓存机制开发DRF权限控制类实现权限管理API配置权限白名单 前置配置 在开始开发权限…...
