c++之CRTP

CRTP概述

CRTP,即奇异递归模板模式(Curiously Recurring Template Pattern),由James O. Coplien在其1995年的论文中首次提出,是C++中一个独特而强大的设计模式。它利用模板和继承的特性,允许在编译时进行多态操作,从而提高代码的性能和灵活性。

代码示例

template<typename T>
class base {
public:virtual ~base(){}void interface(){static_cast<T*>(this)->imp();}
};class derived:public base<derived> {
public:void imp(){cout << " hello world " << endl;}
};

多态实现对比

C++ 通过虚函数实现多态,但是虚函数会影响类的内存布局,并且虚函数的调用会增加运行时的开销。具体为:

  1. 每一个虚函数都需要额外得指针寻址
  2. 虚函数表现为多态时不能内敛,如果小函数多得话有比较大得性能损失
  3. 每个对象都需要额外得虚指针指向虚表

在C++编程中,静态多态(Static Polymorphism)是一种使用模板实现的编译时多态。CRTP作为实现静态多态的有效方式,通过模板类和继承机制,使得子类可以在不增加运行时开销的情况下重用和扩展基类的功能,提高性能。

应用

1.将某个类变为单例

template<typename T>
class singlePatternTemplate
{
public:virtual ~singlePatternTemplate() {}singlePatternTemplate(const singlePatternTemplate&) = delete;singlePatternTemplate & operator=(const singlePatternTemplate&) = delete;static T& getSingleObj()
{static T obj;return obj;}
protected:singlePatternTemplate(){}
};class derivedDemo :public singlePatternTemplate<derivedDemo>
{friend singlePatternTemplate<derivedDemo>;
private:derivedDemo(){}
};

2.静态多态


#include <iostream>// CRTP基类模板template<typename Derived>class Shape {public:void draw() {static_cast<Derived*>(this)->doDraw();  // 静态转换为派生类并调用doDraw()}};// 派生类,继承自Shape模板,使用自身作为模板参数class Circle : public Shape<Circle> {
public:void doDraw() {std::cout << "Drawing a circle" << std::endl;}};class Square : public Shape<Square> {public:void doDraw() {std::cout << "Drawing a square" << std::endl;}};int main() {Circle circle;Square square;circle.draw();  // 输出 "Drawing a circle"square.draw();  // 输出 "Drawing a square"return 0;
}

CRTP与权限控制

当实现CRTP类时,我们不得不担心权限问题,任何你想要调用的方法都应该是可访问的。

对于CRTP方法必须是公共的或者调用方具体特殊的访问权限。这与虚函数调用权限有所不同:虚函数调用方必须有权限访问函数中需要的成员函数。

见如下代码:

template <typename D> class B {
public:void f(int i) { static_cast<D*>(this)->f_impl(i); }
private:void f_impl(int i) {}
protected:int i_;
};
class D : public B<D> {
private:void f_impl(int i) { i_ += i; }friend class B<D>; // 没有此句编译失败
};

f_impl为private,B必须有权限方法D中的成员函数,所以我们要通过友元的方式使调用方(这里是基类)有权限访问此派生类成员函数。

考虑如下D1派生类代码:

class D1 : public B<D> { // 注意这里继承public B<D>不是B<D1>
private:void f_impl(int i) { i_ -= i; }friend class B<D1>;
};

上述代码当对B<D1>进行调用时才会在编译期出现错误:

// 实际调用此句时编译才会失败,实际上不允许D1派生B<D>。
// 我们要考虑即使不调用此句,也需要编译器产生编译失败
// 提示开发者不能使用class D1 : public B<D>来编写D1
B<D1> *b = new D1;
b->f(1);

如果不调用B<D1>*b =new D1;是不会产生编译错误的,我们如果想在不调用B<D1>时就产生编译错误,怎么办呢?我们将属性变为私有,同时将模板类当作友元便可:

template<typename D>
class B {
private:int i_; // 注意i_原来是protected的,现在变为privatefriend D; // 模板作为友元public:void f(int i) { static_cast<D *>(this)->f_impl(i); }private:void f_impl(int i) {}private:int i_; // 由protected变为私有
};class D : public B<D> {...
}class D1 : public B<D> { // 注意这里继承public B<D>不是B<D1>
private:// 这时由于i_是私有属性,同时D1不是B的友元,// 所以这时候无论调用D1与否都会产生编译错误void f_impl(int i) { i_ -= i; }friend class B<D1>;
};

由于i_是私有属性,同时D1不是B的友元,所以这时候无论调用D1与否都会产生编译错误。

注意:

(1)这里不是为了D1能正常调用,B<D1>*b =new D1错误是因为B<D1>并不是D1的基类!

(2)如果不将i_声明为私有属性,那么只有明确写出B<D1>*b =new D1时才会出现编译错误

(3)现在的目标是即使不明确写出B<D1>*b =new D1也要编译器显式的编译错误提醒开发者不能实现class D1 : public B<D>。

标准库的使用

std::enable_shared_from_this部分源码实现

template<typename _Tp>class enable_shared_from_this{protected:enable_shared_from_this(const enable_shared_from_this&) noexcept { }~enable_shared_from_this() { }public:shared_ptr<_Tp>shared_from_this(){ return shared_ptr<_Tp>(this->_M_weak_this); }shared_ptr<const _Tp>shared_from_this() const{ return shared_ptr<const _Tp>(this->_M_weak_this); }private:mutable weak_ptr<_Tp>  _M_weak_this;};
struct Good: std::enable_shared_from_this<Good> // 注意:继承
{std::shared_ptr<Good> getptr() {return shared_from_this();}
};struct Bad
{// 错误写法:用不安全的表达式试图获得 this 的 shared_ptr 对象std::shared_ptr<Bad> getptr() {return std::shared_ptr<Bad>(this);}
};

利用CRTP实现访问者模式

template<typename T>
struct visitor
{virtual void visit(T*) = 0;
};struct visitor_token{virtual ~visitor_token() = default;
};struct animal{virtual int move()=0;virtual void accept(visitor_token*) = 0;virtual ~animal() = default;
};//crtp
template<typename T>
struct visitable : public animal{void accept(visitor_token* v) override {dynamic_cast<visitor<T>*>(v)->visit(static_cast<T*>(this));}
};struct dog : public visitable<dog>{int move() override {return 4;}void swim(){std::cout<<"swim"<<std::endl;}
};struct bird : public visitable<bird>{int move() override {return 2;}void fly(){std::cout<<"fly"<<std::endl;}
};struct fish : public visitable<fish>{int move() override {return 1;}void dive(){std::cout<<"dive"<<std::endl;}
};template<class... T>
struct MultipleVisitor : public visitor_token, public visitor<T>...
{
};using MyVisitor = MultipleVisitor<dog,bird>;
using MyVisitor1 = MultipleVisitor<fish>;struct visitor_impl : public MyVisitor{void visit(dog* d) override{d->swim();}void visit(bird* b) override{b->fly();}
};struct visitor_impl1 : public MyVisitor1{void visit(fish* f) override{f->dive();}
};int main()
{animal* a = new dog;visitor_token* v = new visitor_impl;a->accept(v);animal* b = new bird;b->accept(v);visitor_token* v1 = new visitor_impl1;animal* c = new fish;c->accept(v1);
}

局限

既然CRTP能实现多态性,且其性能优于virtual,那么virtual还有没有存在的必要么?

虽然CRTP最终还是调用派生类中的成员函数。但是,问题在于Base类实际上是一个模板类,而不是一个实际的类。因此,如果存在名为Derived和Derived1的派生类,则基类模板初始化将具有不同的类型。这是因为,Base类将派生自不同的特化,即 Base,代码如下:

class Derived : public Base<Derived> {void imp(){std::cout << "in Derived::imp" << std::endl;}
};class Derived1 : public Base<Derived1> {void imp(){std::cout << "in Derived1::imp" << std::endl;}
};

如果创建Base类模板的指针,则意味着存在两种类型的Base指针,即:

// CRTP
Base<Derived> *b = new Derived;
Base<Derived> *b1 = new Derived1;

显然,这与我们虚函数的方式不同。因为,动态多态性只给出了一种Base指针。但是现在,每个派生类都可以使用不同的指针类型。

// virtual
Base *v1 = new Derived;
Base *v2 = new Derived1;

正是因为基于CRTP方式的指针具有不同的类型,所以不能将CRTP基类指针存储在容器中,下面的代码将编译失败:

int main() {Base<Derived> *d = new Derived;Base<Derived> *d1 = new Derived1;auto vec = {d, d1};return 0;
}

编译器输出如下:

test.cc: In function ‘int main()’:
test.cc:33: error: cannot convert ‘Derived1*’ to ‘Base<Derived>*’ in initialization
test.cc:35: error: ISO C++ forbids declaration of ‘vec’ with no type
test.cc:35: error: scalar object ‘vec’ requires one element in initializer

正是因为其局限性,所以CRTP是一种特殊类型的多态性,在少数情况下可以替代动态多态性的需要。

输出结果完全符合预期,但是这样实现,可能存在以下两个问题:

• 性能损失:因为使用了virtual来实现此功能,而virtual函数会涉及到vtables等,所以如果频繁调用,性能会有影响

• 重复代码:为了实现这个功能,Derived和Derived1都需要在其函数体内实现PrintType()函数,如果派生类非常多的话,每个派生类都要实现该功能,冗余代码太多

总结

     通过CRTP技术,在某种程度上也可以实现多态功能,但其也仅限定于使用场景,正如局限性一节中所提到的,CRTP是一种特殊类型的多态性,在少数情况下可以替代动态多态性的需要;另外,使用CRTP技术,代码可读性降低、模板实例化之后的代码膨胀以及无法动态绑定(在编译期决实例化),因此,我们可以根据使用场景,来灵活选择CRTP或者virtual来达到多态目的。

参考

modern c++设计模式系列(一)

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

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

相关文章

virutalBox安装debian并配置docker环境

下载镜像 https://gemmei.ftp.acc.umu.se/debian-cd/current/amd64/iso-cd/debian-12.5.0-amd64-netinst.iso 虚拟机安装 如何在Virtual Box 上安装Debian系统_virtual box debian iso netinst-CSDN博客 启动命令行模式 如何设置Debian图形启动或命令行界面启动&#xff1…

什么是Cookie?有什么用?如何清除浏览器中的Cookie?

互联网上的每一次点击和每一个选择都可能被一种名为Cookie的技术记录下来。但Cookie是什么&#xff1f;我们在网站上登录时&#xff0c;为什么经常会被问及是否接受Cookie&#xff1f;接受Cookie登录会不会影响我们的在线隐私&#xff1f; Cookie是什么&#xff1f; Cookie是一…

设计模式3-分类

设计模式-分类 模式的分类从目的来分创建型模式&#xff08;Creational Patterns&#xff09;结构型模式&#xff08;Structural Patterns&#xff09;行为型模式&#xff08;Behavioral Patterns&#xff09; 从范围来分类模式&#xff08;Class Patterns&#xff09;对象模式…

S-Clustr(影子集群)V3 高并发,去中心化,多节点控制

S-Clustr 项目地址:https://github.com/MartinxMax/S-Clustr/releases/tag/S-Clustr-V3.0 Maptnh Не ограничивайте свои действия виртуальным миром. GitHub: Maptnh Jay Steinberg Man kann die Menschen, die man hasst, in d…

体育赛事翻译欧洲杯足球翻译术语分享

欧洲杯又称欧洲足球锦标赛&#xff0c;是世界上受欢迎和具影响力的国际体育赛事之一&#xff0c;有关足球翻译的术语分享如下&#xff1a; penalty mark (点球)罚球点,midfielder 前卫,center forward 中锋 full back 后卫,bicycle kick / overhead kick 倒钩球,chest-high ba…

如何用Vue3打造一个交互式数据统计仪表盘

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 代码相关技术博客 1. 代码应用场景介绍 本代码示例展示了一个用于展示统计数据的仪表盘界面。它适用于需要可视化跟踪和分析各种指标的应用程序&#xff0c;例如财务管理、健康监测和商业智能。 2. 代码基本…

机器学习-保存模型并根据模型进行预测 python demo

文章目录 前言机器学习-保存模型&#xff0c;根据模型进行预测python demo1. 将我们创建的线性回归模型保存到本地2. 利用我们保存的模型进行房价预测 demo2. 利用我们保存的模型生成对应的预测线性图 demo 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评…

国密SSL证书提升网络安全

随着数字化时代的到来&#xff0c;网络安全已经成为全球关注的焦点。在这种背景下&#xff0c;SSL证书作为保护数据传输安全的重要工具&#xff0c;其重要性日益凸显。 数字证书产品有以下几种类别&#xff1a; 单域名SSL证书&#xff1a;为单一网站提供安全保护。 多域名SS…

恶意软件识别

恶意软件识别是保护计算机系统和用户数据安全的重要步骤。以下是关于恶意软件识别的详细分析&#xff1a; 一、恶意软件的定义 恶意软件&#xff08;Malware&#xff09;是指那些被用来对计算机系统造成破坏或者以掩盖本身恶意活动为目的进行隐藏的软件。这些软件会操控、破坏…

基于HandyControl实现侧边菜单动态加载TabItem的功能

主要功能是点击左侧的 SideMenu 项目&#xff0c;然后在右侧的 TabControl 中创建一个新的 TabItem。这个 TabItem 的内容是一个 TextBlock&#xff0c;显示的是所点击的 SideMenuItem 的 Header 文本。代码还包括了关闭 TabItem 的功能。 以下是具体实现思路&#xff1a; 1.…

代码随想录算法训练营第四十一天|01背包问题 二维 01背包问题 一维 416. 分割等和子集

卡码网 01背包问题 二维 题目链接&#xff1a;01背包问题 二维 踩坑&#xff1a;在考虑当前物品时&#xff0c;应先考虑当前的背包能不能放得下当前物品 思路&#xff1a; 动态数组的含义&#xff1a;dp[i][j]&#xff1a;物品[0, i]在容量为 j 的背包中的最大价值递推公式…

【PyTorch函数解析】einsum的用法示例

一、前言 einsum 是一个非常强大的函数&#xff0c;用于执行张量&#xff08;Tensor&#xff09;运算。它的名称来源于爱因斯坦求和约定&#xff08;Einstein summation convention&#xff09;&#xff0c;在PyTorch中&#xff0c;einsum 可以方便地进行多维数组的操作和计算…

MySQL中服务器状态变量全解(一)

MySQL 服务器维护了许多状态变量&#xff0c;这些变量提供了关于其操作的信息。您可以使用 SHOW [GLOBAL | SESSION] STATUS 语句来查看这些变量及其值。 GLOBAL 关键字&#xff08;可选&#xff09;用于显示所有连接的聚合值。这些值通常表示自MySQL服务器启动以来的累计统计…

DWC USB2.0协议学习1--产品概述

本章开始学习记录DWC_otg控制器&#xff08;新思USB2.0&#xff09;的特点、功能和应用。 新思USB 2.0 IP主要有两个文档需要参考&#xff1a; 《DesignWare Cores USB 2.0 Hi-Speed On-TheGo (OTG) Data book》 《DesignWare Cores USB 2.0 Hi-Speed On-TheGo (OTG) Progra…

Leetcode85 01矩阵中的最大矩形

题目描述 给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵&#xff0c;找出只包含 1 的最大矩形&#xff0c;并返回其面积。 解题思路 动态规划的思想&#xff0c;记录每一个位置向上能到达的最大高度&#xff0c;和向左能到达的最大宽度。 在一个点进行遍历时…

解决IMX6ULL GPIO扩展板PWM7/8中的pwm0/period后卡死问题

前言 本篇文章主要是记录解决百问网论坛上面设置 IMX6ULL GPIO扩展板PWM7/8中的pwm0/period后卡死问题&#xff0c;如下图&#xff1a; 一、查看原理图&#xff0c;找出对应引脚 在这里我们如何确定哪个扩展口中的引脚输出PWM波呢&#xff1f;我们可以通过查看原理图。 查看…

作业6.20

1.已知网址www.hqyj.com截取出网址的每一个部分(要求&#xff0c;该网址不能存入文件中) 2.将配置桥接网络的过程整理成文档&#xff0c;发csdn 步骤i&#xff1a;在虚拟机设置中启用桥接模式 1. 打开VMware虚拟机软件。 2. 选择您想要配置的虚拟机&#xff0c;点击菜单栏中的“…

C++ 基础:指针和引用浅谈

指针 基本概念 在C中&#xff0c;指针是存储其他变量的内存地址的变量。 我们在程序中声明的每个变量在内存中都有一个关联的位置&#xff0c;我们称之为变量的内存地址。 如果我们的程序中有一个变量 var&#xff0c;那么&var 返回它的内存地址。 int main() {int var…

北大医院副院长李建平:用AI解决临床心肌缺血预测的难点、卡点和痛点

2024年6月14日&#xff0c;第六届北京智源大会在中关村展示中心开幕&#xff0c;海内外的专家学者围绕人工智能关键技术路径和应用场景&#xff0c;展开了精彩演讲与尖峰对话。在「智慧医疗和生物系统&#xff1a;影像、功能与仿真」论坛上&#xff0c;北京大学第一医院副院长、…

孩子不想上学,父母应如何教育?“强迫教育”会激起孩子反抗心理

上周末朋友聚会&#xff0c;都是家有上学娃的年纪&#xff0c;闲聊中&#xff0c;话题自然少不了孩子的上学问题。其中&#xff0c;不少朋友都有抱怨过同一个问题&#xff1a;孩子不想上学&#xff0c;即使人到了学校&#xff0c;心也不在学校。   事实上&#xff0c;孩子出现…