当前位置: 首页 > news >正文

【模板的特殊继承关系】 奇异的递归模板模式

一、奇异的递归模板模式范例

奇异的递归模板模式 ( 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()函数是一个静态成员函数或者函数模板的时候,我们就无法使用虚函数进行动态多态,因此就必须使用奇异的递归模板模式了。

http://www.lryc.cn/news/436839.html

相关文章:

  • SAP B1 单据页面自定义 - 用户界面编辑字段
  • MinIO【部署 02】Linux集群版本及Windows单机版、单机多目录版、分布式版(cmd启动脚本及winsw脚本分享)
  • 手握18个大厂offer,我在大模型风口起飞
  • 邦芒忠告:办公室聊天应避开的四个话题
  • 交易型开放式指数基金(ETF)
  • opencv将灰度图转为彩色图片
  • 判断PDF与图片是否可以预览
  • 多线程与并发区别
  • 这个桌面日历真不错 笔记 提醒 生日记录 打卡 翻译都有 真的太方便了!
  • 多模态大语言模型综述(中)-算法实用指南
  • Qt | ubuntu20.04安装Qt6.5.3并创建一个example完整教程(涉及诸多开发细节,商用慎重)
  • 苏州科技大学、和数联合获得国家知识产权局颁发的3项发明专利证书
  • CleanMyMac X2024破解版mac电脑清理工具
  • 微软数据库的SQL注入漏洞解析——Microsoft Access、SQLServer与SQL注入防御
  • 无人机之处理器篇
  • 828华为云征文 | 华为云Flexus X实例上实现Docker容器的实时监控与可视化分析
  • 缓存预热/雪崩/穿透/击穿
  • C/C++:优选算法
  • 用于大数据分析的数据存储格式:Parquet、Avro 和 ORC 的性能和成本影响
  • 【Jupyter Notebook】安装与使用
  • 默认端口被占用后,如何修改Apache2 端口
  • Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(2) (*****生成数据结构类的方式特别有趣****)
  • Idea 中的一些配置
  • VulnHub DC-1-DC-7靶机WP
  • 基于DPU的容器冷启动加速解决方案
  • SOME/IP 通信协议详细介绍
  • 基于Boost库的搜索引擎开发实践
  • 【2023年】云计算金砖牛刀小试3
  • 在以太坊中不同合约之间相互调用的场景有哪些?
  • 关于 PC打开“我的电脑”后有一些快捷如腾讯视频、百度网盘、夸克网盘、迅雷等各种捷方式在磁盘驱动器上面统一删除 的解决方法