【C++】奇异递归模板模式CRTP——静态多态

奇异递归模板模式(Curiously Recurring Template Pattern,CRTP)是C++模板编程时的一种惯用法(idiom):把派生类作为基类的模板参数。更一般地被称作F-bound polymorphism。CRTP在C++中主要有两种用途:

  • 静态多态(static polymorphism)
  • 添加方法,同时精简代码

本文将会对CRTP,进行比较详细的讲解。


CRTP基础

动态多态

C++动态多态,也就是运行时多态。这是利用继承、虚函数、基类的指针指向派生类的对象,来实现的。一个简单的例子:

#include <iostream>class Base {public:virtual void Run() { std::cout << "Base::Run()"; }
};class Derived : public Base {public:void Run() { std::cout << "Derived::Run()"; }
};int main()
{Base *ptr = new Derived();ptr->Run();return 0;
}

此时,Run()函数的输出结果是Derived::Run()。

这就是动态多态,是通过虚指针 -> 虚函数表 -> 虚函数,实现的。在性能上,会有额外的开销。

静态多态

所谓静态多态,是指CRTP通过派生类对基类模板实例化,也可以实现类似动态多态的效果。最鲜明的特点就是:

  1. 继承自模板类;
  2. 使用派生类作为模板参数特化基类;

一个简单的例子:

#include <iostream>template <typename T>
class Base {public:void Run() { static_cast<T*>(this)->Run(); }
};class Derived : public Base<Derived> {public:void Run() { std::cout << "Derived::Run()"; }
};int main()
{Base<Derived> *ptr = new Derived();ptr->Run();return 0;
}

此时,Run()函数的输出结果是Derived::Run()。

有时候为了更简化代码,可以修改成:

template <typename T>
class Base {public:T* cast() { return static_cast<T*>(this); }void Run() { cast()->Run(); }
};class Derived : public Base<Derived> {public:void Run() { std::cout << "Derived::Run()"; }
};

这样,每个派生类都可以实现各自的Run()方法,最终由于指针是基类的指针,并且没有实现虚函数。因此,调用的都是基类的Run()方法。但是基类的Run()方法中,都将指针转为了派生类。最终调用的都是各自派生类的Run()方法。

使用派生类作为模板参数特化基类,这样做的目的是为了基类中使用派生类,从基类的角度来看,派生类其实也是基类,通过向下转换[downcast]。因此,基类可以通过static_cast把其转换到派生类,从而使用派生类的成员,形式如下:

T* derived = static_cast<T*>(this);
T& derived = static_cast<T&>(*this);

这两种方式都是可以使用的。

注意:这里不使用dynamic_cast,因为dynamic_cast一般是为了确保在运行期(run-time)向上向下转换的正确性。CRTP的设计是:派生类就是基类的模板参数,因此static_cast足矣

静态多态实现原理:编译时编译器会为模板生成一份实例化代码,根据对应实例调用对应函数

静态多态与和动态的区别是:多态是动态绑定(运行时绑定run-time binding),CRTP是静态绑定(编译时绑定 compile-time binding)。

其中,动态多态在实现多态时,需要重写虚函数,这种运行时绑定的操作往往需要查找虚表等,效率低。而template的核心技术在于编译期多态机制,与运行期多态相比,这种机制提供编译期多态性,给了程序运行期无可比拟的效率优势。

关于编译期编译期多态和运行期多态效率对比,可以参考文章:The cost of dynamic (virtual calls) vs. static (CRTP) dispatch in C++。

虽然静态多态避免了动态多态的性能开销问题。但是每个模板实例会在编译时生成一份实例化代码,如果使用大量的模板可能会导致 代码膨胀。


CRTP应用

粗略地了解了CRTP的原理和代码基本范式,下面会对几个典型的CRTP的使用场景和应用进行分析,以便更好地理解。

std::enable_shared_from_this

在shared_ptr的学习中,我们会被强调一点:不要将this指针返回给shared_ptr。这是因为,在返回this的sharedptr时,又通过this指针构造了一个shared_ptr,这样就会导致有两个shared_ptr通过不同的控制块,管理相同的对象。一旦其中一个shared_ptr释放了所管理的对象,那么另一个shared_ptr将会变成非法的

不太清楚的可以参考文章:【C++】shared_ptr共享型智能指针详解。

如果想要返回this指针,需要如下操作:

class Frame : public std::enable_shared_from_this<Frame> {public:std::shared_ptr<Frame> GetThis() {return shared_from_this();}
};

至于为什么要这样做,可以参考基类enable_shared_from_this的伪代码:

template<class D>
class enable_shared_from_this {
protected:constexpr enable_shared_from_this() { }enable_shared_from_this(enable_shared_from_this const&) { }enable_shared_from_this& operator=(enable_shared_from_this const&) {return *this;}public:shared_ptr<T> shared_from_this() { return self_; }shared_ptr<T const> shared_from_this() const { return self_; }private:weak_ptr<D> self_;friend shared_ptr<D>;
};template<typename T>
shared_ptr<T>::shared_ptr(T* ptr) {// ...// Code that creates control block goes here.// ...// NOTE: This if check is pseudo-code. Won't compile. There's a few// issues not being taken in to account that would make this example// rather noisy.if (is_base_of<enable_shared_from_this<T>, T>::value) {enable_shared_from_this<T>& base = *ptr;base.self_ = *this;}
} 

这里就是采用CRTP的方法。

Eigen库

为了提升性能,Eigen用了很多模板元编程技术,其中就用到了许多CRTP的编程方法。

同时还使用到了,表达式模板(expression-template)。这也是一种C++模板元编程技术,它在编译时刻构建好一些能够表达某一计算的结构,对于整个计算这些结构仅在需要(惰性计算、延迟计算)的时候才产生相关有效的代码运算。因此,表达式模板允许程序员绕过正常的C++语言计算顺序,从而达到优化计算的目的。

例如:对于解决一个向量加法表达式 v 3 = v 0 + v 1 + v 2 v_3=v_0+v_1+v_2 v3=v0+v1+v2,传统的编程方式存在中间内存的申请与释放、多次循环遍历等空间和时间的低效问题。但是利用奇异递归模板模式(CRTP)、表达式模板(Expression Templates)就可以解决它。

这部分的代码少许复杂,想要了解的可以参考文章:【编程技术】C++ CRTP & Expression Templates。


相关阅读

  • [C++] 深入探索 C++ 多态 ④ - 模板静态多态
  • 奇异递归模板模式( Curiously Recurring Template Pattern,CRTP)1
  • 奇异递归模板模式(CRTP)应用–表达式模板(expression template) 2
  • 奇异递归模板模式(Curiously Recurring Template Pattern)
  • 【C/C++ 奇异递归模板模式 】C++中CRTP模式(Curiously Recurring Template Pattern)的艺术和科学

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

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

相关文章

在高质量视频生成文本、图像生成文本的GLM-4V-Plus技术加持下医疗未来的方向

人工智能的进步为医疗领域带来了巨大的变革&#xff0c;尤其是视频生成文字、图片生成文字和医学统计数据生成文字等技术的应用。这些技术使得我们能够更全面地利用大数据来辅助诊断&#xff0c;为患者提供更加精确和个性化的医疗服务。以下是一些可能的应用场景和优势&#xf…

国产网卡品牌崛起,做好网络信息安全的“守门人”

在信息技术日新月异的时代背景下&#xff0c;信息安全不仅关乎个人隐私保护&#xff0c;更是国家安全与经济发展的基石。LR-LINK联瑞凭借其前瞻性的视野和深厚的研发实力&#xff0c;成功自主研发出全国产化的FPGA&#xff08;现场可编程门阵列&#xff09;网闸隔离卡方案&…

优质企业上网行为管理软件大盘点

员工在上班时间摸鱼&#xff0c;看似是一个小问题&#xff0c;但却会给企业带来诸多不良影响。首先&#xff0c;摸鱼会降低员工的工作效率&#xff0c;导致工作任务无法按时完成&#xff0c;影响项目进度。其次&#xff0c;摸鱼行为会破坏企业的工作氛围&#xff0c;影响其他员…

零基础学习Python(七)

1. 字符串常用方法 lower()、upper()&#xff1a;转换为小写字符串、大写字符串 split(str)&#xff1a;按照指定字符串str进行分割&#xff0c;结果为列表&#xff1a; email "123qq.com" print(email.split("")) [123, qq.com] count(str)&#xf…

Linux 安装Mysql保姆级教程

一、检查环境 我们登录服务器&#xff0c;查看之前是否安装过mysql rpm -qa | grep mysql 由于我之前安装过&#xff0c;所以这里是有数据的 如果需要删除重新下载&#xff0c;可以使用 rpm -e mysql57-community-release-el7-10.noarch.rpm 二、安装 1、下载 接下来下载安装…

Hive SQL

一、基本数据类型 tinyint 1byte 有符号整数 smallint 2byte 有符号整数 int 4byte 有符号整数 bigint 8byte 有符号整数 boolean 布尔类型&#xff0c;true或者false float 单精度浮点数 double 双精度浮点数 decim…

电脑C盘临时文件怎么清理?

在解决“C盘临时文件怎么清理&#xff1f;”的问题前&#xff0c;先来一起了解一下清理C盘临时文件的原因&#xff1a; 释放磁盘空间&#xff1a;临时文件可以占用大量磁盘空间&#xff0c;尤其是在长时间未清理的情况下&#xff0c;清理这些文件可以释放空间。提高系统性能&a…

gaussian grouping训练自定义数据集

gaussian grouping是一个语义分割3DGS的方法。 它在每个3DGS点云中加入一个叫Identity Encoding的特征向量&#xff0c; 在渲染时把特征向量渲染到2D图像&#xff0c;每个像素位置为一个特征向量&#xff0c;使用额外的线性分类层对每个2D位置的特征向量分类。得到mask。 这个m…

Python——模块和包

模块 Python的模块&#xff08;Modules&#xff09;是Python程序的重要组成部分&#xff0c;它们允许你将代码分解成可重用的单元。每个模块都是一个包含Python代码的文件&#xff0c;文件名就是模块名加上.py后缀。模块可以定义函数、类和变量&#xff0c;也可以包含可执行的…

sheng的学习笔记-AI-半监督聚类

AI目录&#xff1a;sheng的学习笔记-AI目录-CSDN博客 半监督学习&#xff1a;sheng的学习笔记-AI-半监督学习-CSDN博客 聚类&#xff1a;sheng的学习笔记-AI-聚类(Clustering)-CSDN博客 均值算法&#xff1a;sheng的学习笔记-AI-K均值算法_k均值算法怎么算迭代两次后的最大…

突发!Runway 从 HuggingFace 及 GitHub 上删库跑路,背后有何隐情?

突发&#xff01;2024年8月29日Runway 从 HuggingFace 及 GitHub 上删库跑路&#xff0c;背后有何隐情&#xff1f; &#x1f9d0; 今天我们来聊一聊科技圈一则爆炸性消息&#xff1a;Runway ML 从 HuggingFace 和 GitHub 上删库跑路&#xff0c;毫无预警&#xff01;这个举动…

Java大文件上传方案(vue+饿了么):秒传、断点续传、分片上传!

前言 本篇文章是基于其他文章的基础上结合自己的理解写出来的,如果哪里有问题请指出! 详细教程 秒传 1、什么是秒传 通俗的说&#xff0c;你把要上传的东西上传&#xff0c;服务器会先做MD5校验&#xff0c;如果服务器上有它就会进入秒传&#xff0c;想要不秒传&#xff0…

properties文件提示未引用

问题描述 以前用的好好的项目,今天突然打开就发现idea不识别spring配置信息显示未引用,如果config代码中引入的配置却可以高亮显示,然后输入spring相关的配置,文件是没有提示的。经过研究发现是spring相关的插件被关闭了。效果如下 解决方法 启用三个插件spring Boot,Sp…

看完这100道软件测试面试题,拿不到offer,算我输

掌握此套面试题&#xff0c;人手至少2份offer&#xff0c;绝不瞎吹&#xff01;分享给大家。 一、自我介绍 二、灵活问题 1、大概说说之前公司的测试流程 2、测试报告有哪些内容? 3、如何保证用例的覆盖度&#xff1f; 4、什么是测试用例&#xff0c;什么是测试脚本&…

知识社区的小程序源码系统 界面支持万能DIY装修 带源代码包以及搭建部署教程

系统概述 知识社区的小程序源码系统是一款专为构建知识分享和交流社区而设计的强大工具。它提供了完整的源代码包&#xff0c;使开发者能够根据自己的需求进行定制和扩展&#xff0c;打造出个性化的小程序应用。 该系统的界面设计简洁大方&#xff0c;易于操作&#xff0c;同…

【JavaEE】线程安全性问题,线程不安全是怎么产生的,该如何应对

产生线程不安全的原因 在Java多线程编程中&#xff0c;线程不安全通常是由于多个线程同时访问共享资源而引发的竞争条件。以下是一些导致线程不安全的常见原因&#xff1a; 共享可变状态&#xff1a;当多个线程对共享的可变数据进行读写时&#xff0c;如果没有适当的同步机制&…

鸿蒙Next 单元测试框架——hypium

一 框架概述 单元测试框架(hypium)是HarmonyOS上的测试框架&#xff0c;提供测试用例编写、执行、结果显示能力&#xff0c;用于测试系统或应用接口。 表1 单元测试框架功能特性 二 安装使用 目前hypium以npm包的形式发布, 因此需要在Deveco Studio 工程级package.json内配…

CSS-常用属性【看这一篇就够了】

目录 前言文章 常用属性 cursor鼠标样式 outline外轮廓 border与outline的区别 overflow超出部分隐藏 overflow属性值 overflow-x和overflow-y vertical-align属性 应用案例 常用的a标签布局按钮 水平居中的轮播图按钮 产品展示效果&#xff1a; 小米商城菜单 前…

【C#】属性的声明

在面向对象程序设计中,属性是访问对象存储数据的首选方式。 一般不要直接公开类的变量成员,即便是get访问器和set访问器并无数据访问规则。 属性的声明 1. 完整声明 在代码中输入propfull &#xff0c;并连续按两下tab键 高亮的部分是可以修改的部分&#xff0c;按tab键可以…

FPGA上板项目(四)——FIFO测试

目录 实验内容实验原理FIFO IP 核时序绘制HDL 代码仿真综合实现上板测试 实验内容 理解 FIFO 原理调用 FIFO IP 核完成数据读写 实验原理 FIFO&#xff1a;First In First Out&#xff0c;先入先出式数据缓冲器&#xff0c;用来实现数据先入先出的读写方式。可分类为同步 FI…