【C++奇技淫巧】CRTP(奇特重现模板模式)
CRTP(Curiously Recurring Template Pattern,奇特重现模版模式),是一种在C++中使用模板来实现的设计模式,主要用于实现编译时多态性(静态多态)。这种模式通过类模板和模板继承机制来实现,使得派生类在继承时将自身作为基类模板的参数。
这里采用的中文名字参考了zh.cppreference.com
示例解析
template <typename T>
class Base {
public:void doSomething(){// 访问 Derived 类的成员static_cast<T*>(this)->implementation();}
};class Derived : public Base<Derived> {
public:void implementation(){std::cout << "Derived::implementation()";}
};int main()
{Derived d;d.doSomething();return 0;
}
输出结果:
Derived::implementation()
解析
编译器在编译上面代码时,因为延迟实例化,所以在Derived
派生类后面实例化Base<Derived>
。
在实例化的Base<Derived>
类的doSomething()
函数中,将this
指针从Base<Derived>*
转换成Derived*
,这样就能访问Derived
类中定义的方法了。
CRTP的多态性
C++多态的实现方式分为动态多态(虚函数)、静态多态(模板、函数重载)。
其中用模板实现静态多态的方式也有很多种:
- 函数模板
- 类模板
- CRTP(通过模板继承实现)
动态多态和静态多态
动态多态
动态多态是通过虚函数来实现的。它允许在运行时确定调用哪个对象/方法,这主要依赖于虚函数表(vtable)来动态解析调用。
优点:
- 灵活性:可以在运行时改变对象的行为
- 易于使用:只需声明虚函数,让派生类重写函数即可
缺点:
- 性能开销:每次调用虚函数时都需要通过虚指针转到虚函数表,再找到对应的函数,花费时间长
- 内存开销:每个对象都需要额外的内存来存储指向虚函数表的指针。
静态多态
静态多态通常是通过模板实现的,它在编译时就明确了调用的对象/方法,而不是在运行时。
优点:
- 性能开销:没有运行时的多余开销,函数调用可以直接解析
- 内存开销:运行时不会有多余的内存开销
缺点:
- 代码膨胀:对于每个不同的类型,编译器可能都会生成一份代码,这可能导致最终编译出的二进制文件体积增大
- 灵活性较低:在编译时就已经确定了所有的类型,不能像动态多态那样在运行时改变对象的行为
CRTP和虚函数
性能与开销
- CRTP:使用 CRTP 实现的是静态多态,即在编译时就解析函数调用,而不需要虚函数表,从而避免了运行时的查找开销。这种方法通过模板实例化直接绑定函数,因此运行效率更高,没有额外的内存开销。
- 虚函数:动态多态通过虚函数实现。虚函数依赖于虚函数表(vtable),这意味着每次调用虚函数时都需要通过虚函数表进行间接跳转。这种间接性带来运行时开销,同时每个使用虚函数的对象需要额外的内存来存储指向虚函数表的指针。
灵活性和可维护性
- CRTP:CRTP 允许基类通过模板机制访问派生类的成员,这样可以将一些通用的功能封装在基类中,而具体的实现则在派生类中完成。CRTP 特别适合于实现混入(Mixins)类型的功能,能够在不修改原有类结构的情况下,为类增加额外的功能。
- 虚函数:虚函数提供了极高的灵活性,允许在运行时决定对象的行为。这使得代码可以容易地扩展和修改,但可能导致代码维护和理解的复杂度增加。
代码重用与扩展
- CRTP:CRTP 允许基类通过模板机制访问派生类的成员,这样可以将一些通用的功能封装在基类中,而具体的实现则在派生类中完成。CRTP 特别适合于实现混入(Mixins)类型的功能,能够在不修改原有类结构的情况下,为类增加额外的功能。
上面提到的混入(Mixins)指的是在面向对象编程中用于增加类功能的技术,它通过多重继承将功能模块(Mixin类)混入到一个类中。这种方法允许程序员在不修改原始类代码的情况下为类添加新的行为和属性。
- 虚函数:通过虚函数,派生类可以覆盖基类中的行为,实现功能的自定义和扩展。这种方法简洁直观,易于理解和使用。
类型安全和错误检测
- CRTP:由于 CRTP 是在编译时处理的,所以相关的类型错误会在编译阶段被捕捉和报告,增加了开发过程中的类型安全性。
- 虚函数:虚函数的错误(如类型不匹配)通常在运行时发现,有时这可能导致程序崩溃或不稳定。
CRTP的案例展示
enable_shared_from_this
enable_shared_from_this
就是一个典型的CRTP案例,继承enable_shared_from_this
类的对象可以从内部生成std::shared_ptr
。
这个模板类最初是Boost库引入的,后面被纳入到C++标准库中,下面我们展示Boost库下的源码
namespace boost
{template<class T> class enable_shared_from_this
{
protected:BOOST_CONSTEXPR enable_shared_from_this() BOOST_SP_NOEXCEPT{}BOOST_CONSTEXPR enable_shared_from_this(enable_shared_from_this const &) BOOST_SP_NOEXCEPT{}enable_shared_from_this & operator=(enable_shared_from_this const &) BOOST_SP_NOEXCEPT{return *this;}~enable_shared_from_this() BOOST_SP_NOEXCEPT // ~weak_ptr<T> newer throws, so this call also must not throw{}public:shared_ptr<T> shared_from_this(){shared_ptr<T> p( weak_this_ );BOOST_ASSERT( p.get() == this );return p;}shared_ptr<T const> shared_from_this() const{shared_ptr<T const> p( weak_this_ );BOOST_ASSERT( p.get() == this );return p;}weak_ptr<T> weak_from_this() BOOST_SP_NOEXCEPT{return weak_this_;}weak_ptr<T const> weak_from_this() const BOOST_SP_NOEXCEPT{return weak_this_;}public: // actually private, but avoids compiler template friendship issues// Note: invoked automatically by shared_ptr; do not calltemplate<class X, class Y> void _internal_accept_owner( shared_ptr<X> const * ppx, Y * py ) const BOOST_SP_NOEXCEPT{if( weak_this_.expired() ){weak_this_ = shared_ptr<T>( *ppx, py );}}private:mutable weak_ptr<T> weak_this_;
};} // namespace boost
下面展示使用标准库中的enable_shared_from_this
示例:
#include <memory>
#include <iostream>class MyClass : public std::enable_shared_from_this<MyClass> {
public:std::shared_ptr<MyClass> get_shared_ptr() {return shared_from_this();}
};int main() {std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();std::shared_ptr<MyClass> another_ptr = ptr->get_shared_ptr();std::cout << "Same object: " << (ptr == another_ptr) << std::endl;
}
日志类
template<typename T>
class Logging {
public:void log(const std::string& message) const {// 获取派生类的名称,这里假设派生类有一个名为 getName 的方法std::cout << static_cast<const T*>(this)->getName() << ": " << message << std::endl;}
};class Car : public Logging<Car> {
public:Car(const std::string& model) : model_(model) {}std::string getName() const { return model_; }void drive() {this->log("Starting the engine.");// 驾驶逻辑...this->log("Stopped the engine.");}private:std::string model_;
};class Robot : public Logging<Robot> {
public:Robot(const std::string& identifier) : identifier_(identifier) {}std::string getName() const { return identifier_; }void operate() {this->log("Activating robot.");// 操作逻辑...this->log("Deactivating robot.");}private:std::string identifier_;
};int main() {Car car("Toyota Camry");car.drive();Robot robot("Android 007");robot.operate();return 0;
}
总结
我总结了CRTP的具体应用场景:
- 使用虚函数有性能问题,需要优化,可以将虚函数转成CRTP(后续文章会介绍)
- 能够抽象出通用方法,派生类只使用方法,不重写/修改方法
- 需要基于现有的通用方法来添加新功能(不改变通用方法)
其实在使用CRTP和虚函数的核心区别是,CRTP的继承关系中能够抽象出通用方法,且不会修改通用方法;而虚函数是抽象出通用方法,但要重写方法,只是把通用方法当做一个入口。
相关文章:
【C++奇技淫巧】CRTP(奇特重现模板模式)
CRTP(Curiously Recurring Template Pattern,奇特重现模版模式),是一种在C中使用模板来实现的设计模式,主要用于实现编译时多态性(静态多态)。这种模式通过类模板和模板继承机制来实现,使得派生…...
web学习笔记(六十一)
目录 如何使用公共组件来编写页面 如何使用公共组件来编写页面 1.导入公共组件nav.vue import Catenav from "/components/nav.vue"; 2.在页面插入子组件 如果使用了setup语法糖此时就可以直接在页面插入 <Catenav ></Catenav>标签, …...
Nginx在Docker中的应用:容器化部署与扩展
在当今的云计算和微服务时代,Docker容器技术因其轻量级、可移植性和可扩展性而受到广泛关注。Nginx,作为一个高性能的HTTP和反向代理服务器,也在Docker中找到了其广泛的应用场景。本文将探讨Nginx在Docker中的容器化部署和扩展策略࿰…...
vscode编译和调试wsl环境的c语言程序
直接f5会报错,提示你改一下json文件 launch.json { “version”: “0.2.0”, “configurations”: [ { “name”: “(gdb) Launch”, “type”: “cppdbg”, “request”: “launch”, “program”: “ w o r k s p a c e F o l d e r / a . o u t " , " …...

(CPU/GPU)粒子继承贴图颜色发射
GetRandomInfo节点(复制贴进scratch pad Scripts) Begin Object Class/Script/NiagaraEditor.NiagaraClipboardContent Name"NiagaraClipboardContent_22" ExportPath/Script/NiagaraEditor.NiagaraClipboardContent"/Engine/Transient.NiagaraClipboardConten…...
【C#】 一个窗体能够显示、最小化、最大化、关闭时分别触发方法
在C#的WPF应用程序中,窗体(即继承自System.Windows.Window的类)能够通过处理以下事件来响应显示、最小化、最大化和关闭操作: 1.显示: 窗体显示时没有直接对应的事件,但你可以通过覆盖OnLoaded方法或订阅…...
pgsql基本操作
查看已经存在的数据库 postgres# \lList of databasesName | Owner | Encoding | Collate | Ctype | Access privileges ----------------------------------------------------------------------postgres | postgres | UTF8 | C | C | runoobdb …...

3d渲染的常用概念和技术,渲染100邀请码1a12
之前我们介绍了3D渲染的基本原理和流程,这次说下几个常用概念和技术。 3D渲染中涉及到很多专业的概念和技术,它们决定了渲染质量和效果,常用的有以下几个。1、光线追踪 光线追踪是一些专业渲染器(如V-Ray和Corona等)…...

热敏电阻的设计
热敏电阻(NTC)的作用:抑制开机时的浪涌电流。防止开机瞬间产生的浪涌电流损坏后面的元件。 取值依据:根据对开机的脉冲电流(浪涌电流)小于多少A? 由,这个U是指最大输入电压,I为要求的浪涌电流。 NTC是负温度系数的热…...

macOS上编译android的ffmpeg及ffmpeg.c
1 前言 前段时间介绍过使用xcode和qt creator编译调试ffmepg.c,运行平台是在macOS上,本文拟介绍下android平台如何用NDK编译链编译ffmepg库并使用。 macOS上使用qt creator编译调试ffmpeg.c macOS上将ffmpeg.c编译成Framework 大体思路: 其…...

RxSwift - 实现一个MVVM架构的TableView
文章目录 RxSwift - 实现一个MVVM架构的TableView前沿MVVM架构的Tableview目录结构1、模型(Model)2、视图模型(ViewModel)3、视图(View) 界面效果 RxSwift - 实现一个MVVM架构的TableView 前沿 MVVM架构在…...
在 CentOS 7 上安装并配置 Redis 允许远程连接的详细教程
第一部分:安装 Redis Redis 是一款高性能的键值存储系统,广泛应用于缓存、消息队列及数据库场景。下面是如何在 CentOS 7 系统上安装 Redis 的步骤。 步骤1:安装 EPEL 仓库 EPEL (Extra Packages for Enterprise Linux) 提供了许多 CentOS 默…...

越来越多企业选择开源批发订货系统
在当今竞争激烈的市场环境中,越来越多的企业选择开源批发订货系统来提高运营效率、降低成本并实现业务的数字化转型。以下是开源批发订货系统的四大优势及其重要功能: 首先,开源批发订货系统具有高度的灵活性和定制性。由于其源代码开放&…...

KT6368A双模蓝牙芯片上电到正常发送AT指令或指令复位需要多久
一、简介 KT6368A芯片上电到正常发送AT指令,或者开启蓝牙广播被搜索到,或者指令复位需要多久等等系列问题总结 详细描述 其实这些问题归结到一起,就还是一个问题,芯片上电需要多久的时间 在另外一份文档里面,是有描…...
代码随想录算法训练营第38天 | 509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯
代码随想录算法训练营第38天 | 509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯 理论基础自己看到题目的第一想法看完代码随想录之后的想法 链接: 509. 斐波那契数 链接: 70. 爬楼梯 链接: 746. 使用最小花费爬楼梯 理论基础 五部曲: 1.确定dp数组…...

变现实谈,我要的不是灵光一现,而是真实的实现!——感悟篇
变现要的是行动不是想法 正文时代奇点奇迹 点题以己及人 正文 每当我看到了一个有趣的事情 我会在脑中构思一些想法 会贴合我当下的想要做的事情 比如 在我写下这篇文章之前 我看到了 二战期间的诞生的一个奇迹 可口可乐 我就思考 咦 原来可口可乐居然是在这么个时间点成长…...

Matlab操作Excel筛选指定数据的对应数据
Matlab中在表格中寻找指定汉字,并返回其所在行数, 将该行数的另一列提取出来。 目录 一、前言 二、直接在命令行输出 三、保存筛选数据excel 一、前言 源数据excel: 指定汉子:买,得到下面数据: 二、直接…...
对于C++STL及其时间复杂度的总结
由于本次在山东CCPC邀请赛中,对于堆的时间复杂度记忆不清晰,导致第4题没有做出来,与铜牌失之交臂,故觉应整理STL的时间复杂度。 本文仅整理有用(竞赛)的stl及其用法,并且不阐述过于基础的内容。…...

Docker搭建FRP内网穿透服务器
使用Docker搭建一个frp内网穿透 在现代网络环境中,由于防火墙和NAT等原因,内网设备无法直接被外网访问。FRP (Fast Reverse Proxy) 是一款非常流行的内网穿透工具,它能够帮助我们将内网服务暴露给外网。本文将介绍如何在Linux服务器上使用Do…...

【NumPy】掌握NumPy的divide函数:执行高效的数组除法操作
🧑 博主简介:阿里巴巴嵌入式技术专家,深耕嵌入式人工智能领域,具备多年的嵌入式硬件产品研发管理经验。 📒 博客介绍:分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...

srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...

AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机
这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机,因为在使用过程中发现 Airsim 对外部监控相机的描述模糊,而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置,最后在源码示例中找到了,所以感…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...