C++ CRTP(奇异递归模板模式)


CRTP 是什么?

一句话总结:CRTP 就是让子类把自己作为模板参数传递给父类

听起来有点绕,直接上代码就明白了:

template <typename Derived>
class Base {// ...
};class Derived : public Base<Derived> {// ...
};

Derived 继承自 Base<Derived>,也就是说,子类把自己“递归”地传给了父类。这就是“奇异递归”的由来。


CRTP 有啥用?

其实 CRTP 最常用的场景有三个:

  1. 静态多态:不用虚函数也能实现类似多态的效果,而且没有虚表,效率高。
  2. 代码复用:基类写通用逻辑,子类只需要实现自己的部分。
  3. 每个子类独立的静态成员:比如计数器,每个子类都有自己的静态变量。

CRTP 的原理

CRTP 的核心原理其实很简单,就是利用了 C++ 模板的“编译期展开”特性,让基类在编译时就能知道派生类的类型。

1. static_cast 的作用

在 CRTP 里,基类通常会用 static_cast<Derived*>(this) 把自己转换成派生类指针,然后调用派生类的方法。这样,虽然代码写在基类里,但实际调用的是派生类的实现。

比如:

void interface() {static_cast<Derived*>(this)->implementation();
}

这行代码在编译期就能确定 Derived 的类型,所以没有虚表,也没有运行时开销。

2. 编译期多态的本质

CRTP 实现的是“静态多态”,也就是多态的分发发生在编译期,而不是运行时。模板展开时,基类里的 static_cast<Derived*> 会被替换成具体的派生类类型,所有调用都在编译时就确定了。

3. 代码复用和静态接口约束

  • 代码复用:基类可以写通用的逻辑,比如日志、计数、接口包装等,具体实现交给派生类。这样不同的派生类可以复用同一套基类逻辑。
  • 静态接口约束:如果派生类没有实现基类里要调用的方法(比如 implementation()),编译时就会报错。这其实是一种“编译期接口检查”,比传统的虚函数更早发现问题。

一个简单的例子

假如我有一堆不同的动物,每种动物都能“说话”,但我又不想用虚函数(比如对性能有要求),CRTP 就能派上用场:

#include <iostream>template <typename Derived>
class Animal {
public:void speak() {static_cast<Derived*>(this)->speak_impl();}
};class Dog : public Animal<Dog> {
public:void speak_impl() {std::cout << "汪汪!" << std::endl;}
};class Cat : public Animal<Cat> {
public:void speak_impl() {std::cout << "喵喵!" << std::endl;}
};int main() {Dog d;Cat c;d.speak(); // 汪汪!c.speak(); // 喵喵!return 0;
}

这里的 Animal 基类里有个 speak(),但真正的实现是在子类里。通过 static_cast<Derived*>(this),基类可以“静态”地调用子类的方法。这样既有多态的效果,又没有虚函数的开销。


CRTP 实例

1. 每个子类独立计数

有时候我想统计每种类型各自创建了多少对象,CRTP 也能轻松搞定:

template <typename Derived>
class Counter {
public:static int count;Counter() { ++count; }
};
template <typename Derived>
int Counter<Derived>::count = 0;class Apple : public Counter<Apple> {};
class Banana : public Counter<Banana> {};int main() {Apple a1, a2;Banana b1;std::cout << Apple::count << std::endl;  // 输出2std::cout << Banana::count << std::endl; // 输出1
}

每个子类都有自己的静态成员变量,互不影响。

2 . 日志

#include <iostream>
#include <string>// CRTP 日志基类
template <typename Derived>
class LoggerBase {
public:void runWithLog(const std::string& opName) {std::cout << "[LOG] 开始操作: " << opName << std::endl;static_cast<Derived*>(this)->run();  // 调用派生类的 run()std::cout << "[LOG] 结束操作: " << opName << std::endl;}
};// 业务类A
class MyAlgorithm : public LoggerBase<MyAlgorithm> {
public:void run() {std::cout << "算法A正在运行..." << std::endl;}
};// 业务类B
class MyService : public LoggerBase<MyService> {
public:void run() {std::cout << "服务B正在处理..." << std::endl;}
};int main() {MyAlgorithm algo;MyService svc;algo.runWithLog("算法A任务");svc.runWithLog("服务B任务");return 0;
}

CRTP 和虚函数的区别

  • 虚函数:运行时多态,有虚表指针,灵活但有点性能损耗。
  • CRTP:编译期多态,没有虚表,效率高,但只能在编译期确定类型。


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

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

相关文章

21.映射字典的值

有时候你会希望保留字典的键不变,但将每个键对应的值应用一个函数进行转换,比如提取字段、做数学运算、格式化等。 ✅ 基本用法 你可以使用 dict.items() 搭配字典推导式或生成器表达式来实现。 def map_values(obj, fn):return dict((k, fn(v)

【算法】贪心算法:摆动序列C++

文章目录前言题目解析算法原理代码示例策略证明前言 题目的链接&#xff0c;大家可以先试着去做一下再来看一下思路。376. 摆动序列 - 力扣&#xff08;LeetCode&#xff09; 题目解析 将题目有用的信息划出来&#xff0c;结合示例认真阅读&#xff0c;去理解题目。 我们的摆…

【DOCKER】-6 docker的资源限制与监控

文章目录1、docker的资源限制1.1 容器资源限制的介绍1.2 OOM1.3 容器的内存限制1.3.1 内存限制的相关选项1.4 容器的CPU限制介绍2、docker的监控插件2.1 cadvisor2.2 portainer1、docker的资源限制 1.1 容器资源限制的介绍 默认情况下&#xff0c;容器没有资源的使用限制&…

gcc 源码分析--gimple 关键数据结构

gimple 操作码&#xff0c;支持这些&#xff1a;DEFGSCODE(GIMPLE_symbol, printable name, GSS_symbol). */ DEFGSCODE(GIMPLE_ERROR_MARK, "gimple_error_mark", GSS_BASE) DEFGSCODE(GIMPLE_COND, "gimple_cond", GSS_WITH_OPS) DEFGSCODE(GIMPLE_DEBU…

TDengine GREATEST 和 LEAST 函数用户手册

TDengine GREATEST 和 LEAST 函数用户手册 1. 需求背景 1.1 问题描述 在实际生产过程中&#xff0c;客户经常需要计算三相电流、电压的最大值和最小值。传统的实现方式需要使用复杂的 CASE WHEN 语句&#xff0c;例如&#xff1a; -- 传统方式&#xff1a;计算三相电流最大…

Redis 与数据库不一致问题及解决方案

一、不一致的原因分析 1. 缓存更新策略不当 先更新数据库后删除缓存:删除缓存失败会导致不一致 先删除缓存后更新数据库:并发请求可能导致不一致 缓存穿透:大量请求直接打到数据库,绕过缓存 2. 并发操作问题 读写并发:读请求获取旧缓存时,写请求更新了数据库但未更新缓存…

iOS 加固工具使用经验与 App 安全交付流程的实战分享

在实际开发中&#xff0c;iOS App不仅要安全&#xff0c;还要能被稳定、快速、无误地交付。这在外包、B端项目、渠道分发、企业自用系统等场景中尤为常见。 然而&#xff0c;许多开发者在引入加固工具后会遇到以下困扰&#xff1a; 混淆后App运行异常、不稳定&#xff1b;资源路…

Windows 下 Visual Studio 开发 C++ 项目的部署流程

在Windows环境中使用Visual Studio&#xff08;以下简称VS&#xff09;开发C项目时&#xff0c;“部署”是确保程序能在目标设备上正常运行的关键环节。部署的核心目标是&#xff1a;将编译生成的可执行文件&#xff08;.exe&#xff09;、依赖的动态链接库&#xff08;.dll&am…

yolo8+声纹识别(实时字幕)

现在已经完成了人脸识别跟踪 ✅&#xff0c;接下来要&#xff1a; ✅ 加入「声纹识别&#xff08;说话人识别&#xff09;」功能&#xff0c;识别谁在讲话&#xff0c;并在视频中“这个人”的名字旁边加上「正在讲话」。 这属于多模态识别&#xff08;视觉 音频&#xff09;&a…

DH(Denavit–Hartenberg)矩阵

DH 矩阵&#xff08;Denavit-Hartenberg 矩阵&#xff09;是 1955 年由 Denavit 和 Hartenberg 提出的一种机器人运动学建模方法&#xff0c;用于描述机器人连杆和关节之间的关系。该方法通过在机器人每个连杆上建立坐标系&#xff0c;并用 44 的齐次变换矩阵&#xff08;DH 矩…

Vim的magic模式

在 Vim 中&#xff0c;magic 模式用于控制正则表达式中特殊字符的解析方式。它决定了哪些字符需要转义才能发挥特殊作用&#xff0c;从而影响搜索和替换命令的写法。以下是详细介绍&#xff1a; 一、三种 magic 模式 Vim 提供三种 magic 模式&#xff0c;通过在正则表达式前添加…

Git 使用技巧与原理(一)—— 基础操作

1、起步 1.1 版本控制 版本控制是一种记录一个或若干文件内容变化&#xff0c;以便将来查阅特定版本修订情况的系统。 版本控制系统&#xff08;VCS&#xff0c;Version Control System&#xff09;通常可以分为三类&#xff1a; 本地版本控制系统&#xff1a;大多都是采用某…

软件测试之自动化测试

目录 1.什么是自动化测试 2.web⾃动化测试 2.1驱动 WebDriverManager 3. Selenium 3.1selenium驱动浏览器的⼯作原理 4.常用函数 4.1元素的定位 4.1.1cssSelector选择器 4.2.2xpath 4.2操作测试对象 4.3窗⼝ 4.4等待 4.5浏览器导航 4.6弹窗 4.7文件上传 4.8设置…

sqlserver迁移日志文件和数据文件

sqlserver安装后没有指定日志存储路径或者还原库指定的日志存储位置不理想想要更改&#xff0c;都可以按照这种方式来更换&#xff1b;1.前提准备&#xff1a;数据库的备份bak文件2.查看自己当前数据库的日志文件和数据文件存储路径是否理想选中当前数据库&#xff0c;右键属性…

MFC UI表格制作从专家到入门

文章目录CListCtrl常见问题增强版CGridCtrl&#xff08;第三方&#xff09;第三方库ReoGridCListCtrl 默认情况下&#xff0c;CListCtrl不支持直接编辑单元格&#xff0c;需通过消息处理实现。 1.添加控件到资源视图 在对话框资源编辑器中拖入List Control控件&#xff0c;设…

数字后端APR innovus sroute到底是如何选取宽度来铺power rail的?

吾爱IC社区新一期IC训练营将于7月初开班&#xff08;07.06号晚上第一次直播课&#xff09;&#xff01;社区所有IC后端训练营课程均为直播课&#xff01;全网唯一一家敢开后端直播课的&#xff08;口碑不好招生一定存在困难&#xff0c;自然就无法开直播课&#xff09;&#xf…

LVS集群技术

LVS&#xff08;Linux Virtual Server&#xff09;是一种基于Linux内核的高性能、高可用性服务器集群技术&#xff0c;它通过负载均衡将客户端请求分发到多台后端真实服务器&#xff0c;实现 scalability 和 fault tolerance。LVS工作在传输层&#xff08;OSI Layer 4&#xff…

git项目,有idea文件夹,怎么去掉

要从Git项目中排除.idea文件夹&#xff08;IntelliJ IDEA的配置文件目录&#xff09;&#xff0c;可以通过以下步骤操作&#xff1a; 1. 添加.gitignore规则 在项目根目录创建或编辑.gitignore文件&#xff0c;添加以下内容&#xff1a; .idea/2. 从Git缓存中删除已跟踪的.idea…

springboot+swagger2文档从swagger-bootstrap-ui更换为knife4j及文档接口参数不显示问题

背景 已有springboot项目,且使用的是swagger2+swagger-bootstrap-ui的版本 1.pom依赖如下 <!-- Swagger接口管理工具 --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9…

mysql数据库表只能查询,对于插入、更新、删除操作一直卡住,直到报错Lost connection to MySQL server during query

诊断步骤1. 查看阻塞进程SELECT * FROM performance_schema.metadata_locks WHERE LOCK_STATUS PENDING;SELECT * FROM sys.schema_table_lock_waits;2. 查看当前活动事务SELECT * FROM information_schema.INNODB_TRX;3. 查看进程列表SHOW PROCESSLIST;通过SELECT * FROM in…