C++惯用法之CRTP(奇异递归模板模式)

相关系列文章

C++惯用法之Pimpl

C++之数据转换(全)

目录

1.介绍

2.CRTP的使用场景

2.1.实现静态多态

2.2.代码复用和扩展性

3.总结


1.介绍

        CRTP的全称是Curiously Recurring Template Pattern,即奇异递归模板模式,简称CRTP。CRTP是一种特殊的模板技术和使用方式,是C++模板编程中的一种惯用法。基本特征表现为:基类是一个模板类;派生类在继承该基类时,将派生类自身作为模板参数传递给基类。下面用网上的实例来说明为什么要用CRTP? 比如我要实现一个数学库,如果使用运行时多态来实现向量类Vector,那么代码结构大致如下:

template<typename Type, unsigned Len>
struct VectorBase{virtual void someFunction(){...}...
};
struct Vector3: VectorBase<float, 3>{virtual void someFunction() override {...}
};

         需要注意的是,运行时多态是有开销的,熟悉c++虚函数的人应该就能明白,如果我调用一个虚函数,需要查询对象头部的虚函数表来得到实际函数地址,这个寻址的开销对于一个数学库而言是非常巨大的。而如果使用静态多态,则可以使用如下的代码来实现:

template <typename ChildType> struct VectorBase {ChildType &underlying() { return static_cast<ChildType &>(*this); }inline ChildType &operator+=(const ChildType &rhs) {this->underlying() = this->underlying() + rhs;return this->underlying();}
};
struct Vec3f : public VectorBase<Vec3f> {float x{}, y{}, z{};Vec3f() = default;Vec3f(float x, float y, float z) : x(x), y(y), z(z) {}
};inline Vec3f operator+(const Vec3f &lhs, const Vec3f &rhs) {Vec3f result;result.x = lhs.x + rhs.x;result.y = lhs.y + rhs.y;result.z = lhs.z + rhs.z;return result;
}

        可以看到,静态多态虽然导致代码复用度相较于运行时多态降低了很多,但是相较于完全手写,我们可以利用子类实现的operator+来通过CRTP自动添加operator+=,相当于是做到了运行效率与开发效率的相对平衡。 

        VectorBase是模版基类,派生类Vec3f 继承自VectorBase,并以自身作为模板参数传递给基类, 在基类内部,通过使用static_cast,将this指针转换为模板参数类型T的指针,然后调用类型T的方法imp。static_cast的用法可参考一下博客。

C++之数据转换(全)_c++数据类型转换-CSDN博客

2.CRTP的使用场景

2.1.实现静态多态

C ++支持动态和静态多态。

  • 动态多态性:在这种类型的多态性中,在编译时不知道对象的类型(可能基于用户输入等),因此编译器添加了额外的数据结构来支持这一点。 该标准并未规定应如何实施。C++通过虚函数表实现多态,但是虚函数会影响类的内存布局,并且虚函数的调用会增加运行时的开销。
  • 静态多态性:在这种类型中,对象的类型在编译时本身是已知的,因此实际上无需在数据结构中保存额外的信息。 但是如前所述,我们需要在编译时知道对象的类型。

CRTP 可以实现静态多态,但本质上 CRTP 中的多个派生类并不是同一个基类,因此严格意义上不能叫多态。示例如下:

template<typename T>
class base
{
public:virtual ~base(){}void interface() { static_cast<T*>(this)->impl(); }void impl() { cout << "base impl... " << endl; }
};class derived1 : public base<derived1>
{
public:void impl(){ cout << "derived1 impl... " << endl; }
};class derived2 : public base<derived2>
{
public:void impl() { cout << "derived2 impl..." << endl; }
};template<typename T>
void testDemo(T & base)
{base.interface();
}int main()
{derived1 a1;derived2 a2;testDemo(a1);  //输出:derived1 impl... testDemo(a2);  //输出:derived2 impl... return 0;
}

2.2.代码复用和扩展性

使用 CRTP 可以把重复性的代码抽象到基类中,减少代码冗余。示例代码如下:

template<typename T>
class base
{
public:virtual ~baseDemo(){}void getType() { T& t = static_cast<T&>(*this);cout << typeid(t).name() << endl;} 
};class derived1 : public base<derived1>
{
};class derived2 : public base<derived2>
{
};int main()
{derived1  a1;derived2  a2;a1.getType(); //输出:class derived1a2.getType(); //输出:class derived2return 0;
}

        可以看到,在基类中getType函数中能够获取到派生于base所有类的类型信息,相比于虚函数的方式减少了代码。上面的代码在getType函数不变的情况下,可以任意编写base的扩展类,提高了代码的可重用性和灵活性。

        我们再看一个示例:多态拷贝构造(Polymorphic copy construction)

// Base class has a pure virtual function for cloning
class Shape {
public:virtual ~Shape() {};virtual Shape *clone() const = 0;
};
// This CRTP class implements clone() for Derived
template <typename Derived>
class Shape_CRTP : public Shape {
public:virtual Shape *clone() const {return new Derived(static_cast<Derived const&>(*this));}
};// Nice macro which ensures correct CRTP usage
#define Derive_Shape_CRTP(Type) class Type: public Shape_CRTP<Type>// Every derived class inherits from Shape_CRTP instead of Shape
Derive_Shape_CRTP(Square) {};
Derive_Shape_CRTP(Circle) {};

         传统的实现方式是,基类有个虚拟clone函数,每个继承类实现自己的clone函数功能。依靠CRTP技术,只定义一个就够了,大家通用,一样减少了冗余代码,提高了代码的复用性。

3.总结

  • 优点:省去动态绑定、查询虚函数表带来的开销。通过CRTP,基类可以获得到派生类的类型,提供各种操作,比普通的继承更加灵活。但CRTP基类并不会单独使用,只是作为一个模板的功能。
  • 缺点:使用CRTP需要编写更多的模板代码,增加了代码的复杂度,对于不熟悉模板编程的开发者来说可能会带来一定的学习成本。

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

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

相关文章

【达梦数据库】数据库的方言问题导致的启动失败

问题场景 在项目中采用了hibernate &#xff0c;连接数据库原本为ORACLE&#xff0c;后续打算改造为国产数据库 达梦 链接配置&#xff1a; # 达梦写法&#xff0c; index:driver-class-name: dm.jdbc.driver.DmDriverjdbc-url: jdbc:dm://192.168.220.225:5236/IDX4username:…

Windows 路径长度限制

Windows API 中的路径长度限制是 260 个字符&#xff0c;但实际可用长度会因为几个因素而减少。以下是减少可用字符数的因素&#xff1a; 驱动器标识符&#xff1a;路径通常包括驱动器的标识符&#xff08;如 C:\&#xff09;&#xff0c;这占用了3个字符。 8.3 文件名保留&am…

【QT 5 +Linux下软件生成+qt软件生成使用工具+学习他人文章+第一篇:使用linuxdeployqt软件生成】

【QT 5 Linux下软件生成qt软件生成使用工具学习他人文章第一篇&#xff1a;使用linuxdeployqt软件生成】 1、前言2、实验环境3、自我学习总结-本篇总结1、新手的疑问&#xff0c;做这件事的目的2、了解工具&#xff1a;linuxdeployqt工具3、解决相关使用过程中问题 4、参照文章…

新手想要做好抖音小店,在开店前你需要知道这五点注意事项!

大家好&#xff0c;我是电商小布。 开抖店你说难吗&#xff0c;其实也不难&#xff0c;把需要的材料准备好就可以着手开店。 难的呢&#xff0c;是在小店的运营上边。 所以新手开店想要少出错&#xff0c;少踩坑&#xff0c;一定要提前把店铺的相关注意事项搞清楚。 今天&a…

将yolov8权重文件转为onnx格式并在c#中使用

yolo模型转ONNX 在yolov8中&#xff0c;我们将训练结果的.pt权重文件转换为onnx格式只需要使用ultralytics库中的YOLO类&#xff0c;使用pip安装ultralytics库&#xff0c;然后执行下面python代码 from ultralytics import YOLO# 加载YOLOv8模型 model YOLO("best.pt&q…

锁类型介绍

锁类型详解 在 《Mutex 详解》一文中我们主要介绍了 C11 标准中的互斥量(Mutex)&#xff0c;并简单介绍了一下两种锁类型。本节将详细介绍一下 C11 标准的的锁类型。 C11 标准为我们提供了两种基本的锁类型&#xff0c;分别如下&#xff1a; std::lock_guard&#xff0c;与 …

力扣代码学习日记六

Problem: 66. 加一 思路 给定一个由 整数 组成的 非空 数组所表示的非负整数&#xff0c;在该数的基础上加一。 最高位数字存放在数组的首位&#xff0c; 数组中每个元素只存储单个数字。 你可以假设除了整数 0 之外&#xff0c;这个整数不会以零开头。 示例 1&#xff1a; 输…

Selenium浏览器自动化测试框架详解

selenium简介 介绍 Selenium [1] 是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中&#xff0c;就像真正的用户在操作一样。支持的浏览器包括IE&#xff08;7, 8, 9, 10, 11&#xff09;&#xff0c;Mozilla Firefox&#xff0c;Safari&#xff0c;Google C…

抖店没有流量怎么办?如何提高店铺流量?醒醒团队经验分享!

我是电商珠珠 醒醒团队这个IP已经做了五年多的时间了&#xff0c;团队成员从原来的几个人到现在的70。我们从20年开始入驻抖店&#xff0c;到现在也已经三年多了。期间也会带着学员一起做店&#xff0c;大多数学员的问题就是店开了&#xff0c;但是一直没有流量&#xff0c;没…

深度学习中数据的转换

原始&#xff08;文本、音频、图像、视频、传感器等&#xff09;数据被转化成结构化且适合机器学习算法或深度学习模型使用的格式。 原始数据转化为结构化且适合机器学习和深度学习模型使用的格式&#xff0c;通常需要经历以下类型的预处理和转换&#xff1a; 文本数据&#xf…

台式电脑电源功率越大越费电吗?装机选购多少W电源

要组装一台电脑&#xff0c;我们首先需要选择硬件。 硬件搭配最关键的一点就是CPU和主板的兼容性。 硬件、电源等之间的平衡都需要仔细考虑。 那么台式电脑电源多大功率合适呢&#xff1f; 下面分享组装电脑电源瓦数选购指南&#xff0c;教您正确选择合适的电源瓦数。 让我们来…

docker install private registry 【docker 安装 registry 仅证书认证】

预备条件&#xff1a; 安装docker 我们设定镜像仓库域名为registry01.dev.com 配置/etc/hosts 192.168.23.51 registry01.dev.com安装 registry #!/bin/bashreg_ip$1 reg_n$2 reg_port$3if [ $# -eq 0 ]; thenecho "Usage: $0 [reg_ip] [registry_name]"echo &q…

消息中间件篇之RabbitMQ-消息不丢失

一、生产者确认机制 RabbitMQ提供了publisher confirm机制来避免消息发送到MQ过程中丢失。消息发送到MQ以后&#xff0c;会返回一个结果给发送者&#xff0c;表示消息是否处理成功。 当消息没有到交换机就失败了&#xff0c;就会返回publish-confirm。当消息没有到达MQ时&…

防御保护--VPN

目录 VPN的概述 VPN的分类 VPN的核心技术 --- 隧道技术 VPN其他常用技术 VPN的概述 VPN --- 虚拟专用网 --- 一般指依靠ISP或者其他NSP&#xff0c;也可以是企业自身&#xff0c;提供的一条虚拟网 络专线。这个虚拟的专线是逻辑上的&#xff0c;而不是物理上的&#xff0c;所…

LeetCode 2583. 二叉树中的第 K 大层和

题目链接https://leetcode.cn/problems/kth-largest-sum-in-a-binary-tree/?envTypedaily-question&envId2024-02-23 和我上一篇发的博客类似&#xff0c;通过广度优先遍历二叉树&#xff0c;使用队列存每层的节点值&#xff0c; 最后再做处理&#xff0c;输出第K大层和 …

书生·浦语大模型实战营第二节课作业

使用 InternLM-Chat-7B 模型生成 300 字的小故事&#xff08;基础作业1&#xff09;。 熟悉 hugging face 下载功能&#xff0c;使用 huggingface_hub python 包&#xff0c;下载 InternLM-20B 的 config.json 文件到本地&#xff08;基础作业2&#xff09;。 下载过程 进阶…

Nest.js权限管理系统开发(三)环境变量与配置文件

一般来说数据库的配置包含了一些敏感信息&#xff0c;不宜写在代码中提交到远程仓库&#xff0c;所以我们可以将配置写在配置文件中,然后提交 git 时候将生产环境的配置文件其忽略。我们可以新建.env和.env.prod两个文件分别存放开发与生产环境配置&#xff0c;也可以使用YAML等…

java面试:分布式事务

文章目录 引言I 基础知识1.1 事务(Transaction)1.2 本地事务1.3 分布式事务II 分布式理论2.1 CAP原理2.2 BASE理论2.3 刚柔事务2.4 解决分布事务模型2.5 2PC2.6 3PC2.7 TCC(Try-Confirm-Cancel)补偿事务III Seata 分布式事务

美赛教程:如何参与并胜出数学建模竞赛

一、引言 美国大学生数学建模竞赛&#xff08;MCM/ICM&#xff0c;通常被称为“美赛”&#xff09;是全球范围内最具影响力和挑战性的大学生数学建模竞赛之一。参与美赛不仅能锻炼大学生的数学建模、问题分析、团队协作和英文写作能力&#xff0c;还能为他们未来的学术和职业生…

【Elasticsearch专栏 12】深入探索:Elasticsearch使用索引生命周期管理(ILM)自动化删除旧数据

导言 在Elasticsearch中&#xff0c;随着数据的不断积累&#xff0c;管理旧数据成为了一个重要的问题。为了有效地管理这些数据&#xff0c;Elasticsearch提供了索引生命周期管理&#xff08;ILM&#xff09;功能。ILM允许根据索引的年龄、大小或其他条件自动执行一系列操作&a…