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

MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...

如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...

搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
提升移动端网页调试效率:WebDebugX 与常见工具组合实践
在日常移动端开发中,网页调试始终是一个高频但又极具挑战的环节。尤其在面对 iOS 与 Android 的混合技术栈、各种设备差异化行为时,开发者迫切需要一套高效、可靠且跨平台的调试方案。过去,我们或多或少使用过 Chrome DevTools、Remote Debug…...

数学建模-滑翔伞伞翼面积的设计,运动状态计算和优化 !
我们考虑滑翔伞的伞翼面积设计问题以及运动状态描述。滑翔伞的性能主要取决于伞翼面积、气动特性以及飞行员的重量。我们的目标是建立数学模型来描述滑翔伞的运动状态,并优化伞翼面积的设计。 一、问题分析 滑翔伞在飞行过程中受到重力、升力和阻力的作用。升力和阻力与伞翼面…...

《Docker》架构
文章目录 架构模式单机架构应用数据分离架构应用服务器集群架构读写分离/主从分离架构冷热分离架构垂直分库架构微服务架构容器编排架构什么是容器,docker,镜像,k8s 架构模式 单机架构 单机架构其实就是应用服务器和单机服务器都部署在同一…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...

消防一体化安全管控平台:构建消防“一张图”和APP统一管理
在城市的某个角落,一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延,滚滚浓烟弥漫开来,周围群众的生命财产安全受到严重威胁。就在这千钧一发之际,消防救援队伍迅速行动,而豪越科技消防一体化安全管控平台构建的消防“…...