当前位置: 首页 > news >正文

C++ 中的 **CRTP

CRTP奇特重现模板模式

它的范式基本上都是:父类是模板,再定义一个类类型去继承它。因为类模板不是类,只有实例化后的类模板才是实际的类类型,所以我们需要实例化它,显示指明模板类型参数,而这个参数就是我们定义的类型,也就是子类了。

template<class Dervied>
class Base{};
//类模板

class x : public Base<x> {};//可以传自己x,也可以传别的,比如Int

 CRTP可用于在父类暴露接口,而子类实现该接口,以此实现“编译器多态”,或者“静态多态”

#define  _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include<type_traits>

template <class Dervied>
class Base {
public:
	//公开的接口函数,供外部调用
	void addWater(int n)
	{
		//调用子类的实现函数,要求子类必须实现名为impl()的函数
		//这个函数会被调用来执行具体的操作
		static_cast<Dervied*>(this)->impl(n);
        //很明显进行了类型转换,将this指针(也就是父类的指针),转换为通过模板参数传递的类型,
//也就是子类的指针。这个转换时安全合法的。因为this指针实际上指向一个X类型的对象,
//X类型对象继承了Base<x>的部分,x对象也就包含了Base<x>的部分,
//所以这个转换在编译期是有效的并且合法的
//当你调用了x.addwater()时,实际上是x对象调用了父类Base<X>的成员函数。这个成员函数内部使用了
//static_cast<Dervied*>(this)->impl(n);将this从Base<x>* 转换为x*,然后调用x中的impl()函数
	}
};

class X :public Base<X> {
public:
	void impl(int n) const {
		std::cout << "X设备加了" << n << "毫升水\n";
	}
};

class Y :public Base<Y> {
public:
	void impl(int n) const {
		std::cout << "Y设备加了" << n << "毫升水\n";
	}
};

int main()
{
	X x;
	x.addWater(100);
	Y y;
	y.addWater(50);
}

C++ 中的 **CRTP(Curiously Recurring Template Pattern,奇异递归模板模式)** 是一种通过模板继承实现**静态多态**的技术。它的核心思想是让基类模板以派生类作为模板参数,从而在编译期实现类似虚函数的多态行为,但避免了运行时的性能开销。以下从多个角度详细解释:

---

### 一、CRTP 的基本结构与工作原理
#### 1. 核心代码结构
```cpp
// 基类模板,以派生类作为模板参数
template <typename Derived>
class Base {
public:
    void interface() {
        // 通过静态转换调用派生类的实现
        static_cast<Derived*>(this)->implementation();
    }
};

// 派生类继承基类,并将自身作为模板参数传递给基类
class Derived : public Base<Derived> {
public:
    void implementation() {
        std::cout << "Derived 的具体实现" << std::endl;
    }
};
```
• **关键点**:  
  • 基类 `Base` 是一个模板类,其模板参数是派生类 `Derived`。  
  • 基类通过 `static_cast<Derived*>(this)` 将自身指针转换为派生类指针,从而调用派生类的方法。

#### 2. 静态多态的实现
• **传统虚函数**:通过虚函数表(vtable)在运行时动态绑定,产生性能开销。  
• **CRTP**:在编译期完成类型绑定,直接调用派生类的实现,没有运行时开销。  
  ```cpp
  Derived d;
  d.interface();  // 编译期确定调用 Derived::implementation()
  ```

---

### 二、CRTP 的优势与典型应用场景
#### 1. 性能优势
• **避免虚函数开销**:虚函数需要查表(vtable)和间接调用,CRTP 直接编译期绑定,适合高频调用的场景(如数学库、游戏引擎)。  
• **示例**:向量运算中,基类通过 CRTP 调用派生类的运算符重载,实现高效的静态多态。

#### 2. 应用场景
##### (1) 静态多态(替代虚函数)
```cpp
template <typename Derived>
class Logger {
public:
    void log(const std::string& message) {
        static_cast<Derived*>(this)->writeLog(message);
    }
};

class ConsoleLogger : public Logger<ConsoleLogger> {
public:
    void writeLog(const std::string& msg) {
        std::cout << "[Console] " << msg << std::endl;
    }
};
```
• 通过 `Logger` 基类统一接口,不同派生类实现具体的日志写入逻辑。

##### (2) 对象计数器
```cpp
template <typename T>
class Counter {
    static int count;  // 每个派生类有独立的计数器
public:
    Counter() { count++; }
    ~Counter() { count--; }
    static int getCount() { return count; }
};

class A : public Counter<A> {};  // A 的计数器
class B : public Counter<B> {};  // B 的计数器
```
• 每个派生类维护独立的静态变量,避免传统继承中所有子类共享同一计数的问题。

##### (3) 标准库中的应用
• **`std::enable_shared_from_this`**:允许对象安全地返回自身的 `shared_ptr`,其实现依赖 CRTP:
  ```cpp
  class MyClass : public std::enable_shared_from_this<MyClass> {
  public:
      std::shared_ptr<MyClass> getShared() {
          return shared_from_this();  // 通过基类方法获取智能指针
      }
  };
  ```

---

### 三、CRTP 的注意事项与局限性
#### 1. 注意事项
• **模板参数必须正确**:若派生类错误传递其他类型(如 `class Derived2 : public Base<Derived1>`),会导致未定义行为。  
  • 可通过将基类构造函数设为私有,并声明派生类为友元来避免此问题:
    ```cpp
    template <typename T>
    class Base {
    private:
        Base() {}
        friend T;  // 仅允许派生类访问构造函数
    };
    ```

• **名称遮掩(Name Hiding)**:若派生类定义了与基类同名的方法,会覆盖基类方法。需使用 `using` 显式引入基类方法。

#### 2. 局限性
• **编译期绑定**:无法在运行时动态切换派生类类型(如工厂模式中动态创建对象)。  
• **代码膨胀**:每个派生类会生成独立的基类模板实例,可能增加代码体积,但现代编译器可优化此问题。

---

### 四、CRTP 与动态多态的对比
| **特性**       | **CRTP(静态多态)**      | **动态多态(虚函数)**      |
|----------------|--------------------------|---------------------------|
| **性能**       | 无运行时开销              | 有虚函数表查找开销          |
| **灵活性**     | 编译期绑定,类型固定       | 运行时动态绑定,类型可变     |
| **代码复杂度** | 模板语法复杂,可读性较低   | 直观,但虚函数需继承体系     |
| **适用场景**   | 高频调用、性能敏感场景     | 需运行时类型切换的场景       |

---

### 五、总结
CRTP 通过模板继承的“递归”特性,在编译期实现了高效的多态行为。**核心价值**包括:
1. **性能优化**:避免虚函数开销,适合数学运算、高频调用逻辑。  
2. **代码复用**:通过基类模板统一接口,派生类灵活实现细节(如策略模式)。  
3. **类型安全**:编译期检查派生类与基类的关联,减少运行时错误。

**学习建议**:从简单的计数器、日志器入手实践,再逐步探索复杂场景(如自定义数据结构优化)。

http://www.dtcms.com/a/106494.html

相关文章:

  • 鸿蒙定位开发服务
  • 论文浅尝 | Interactive-KBQA:基于大语言模型的多轮交互KBQA(ACL2024)
  • HTML 媒体(Media)学习笔记
  • 使用Apache HttpClient编写Java爬虫
  • Python | 第十一章 | 模块和包 | 面向对象编程_基础部分
  • Java安全基础-反射机制
  • 《AI大模型应知应会100篇》第2篇:大模型核心术语解析:参数、Token、推理与训练
  • 基于微信小程序的智慧乡村旅游服务平台【附源码】
  • 聊聊Spring AI的EmbeddingModel
  • 好文和技术网站记录
  • Java虚拟机面试题:引言
  • 【Zabbix技术系列文章】第⑥篇——Zabbix 高级运维与优化
  • leetcode118.杨辉三角
  • Unity注册表修改分辨率:探索幕后设置与手动调控
  • 学习笔记—数据结构—排序
  • 第十二节课:Python语言程序设计和前阶段复盘总结
  • 数字孪生技术解析:开启虚拟与现实融合新时代
  • 界面架构 - 主流架构(Qt)
  • 动态取消Spring Boot通过注解@EnableScheduling启动的定时任务
  • MySQL(1)
  • 【图像处理基石】什么是RAW格式?
  • React DndKit 实现类似slack 类别、频道拖动调整位置功能
  • # BERT架构及详解
  • C# 中实现不同程序进程间消息交互
  • 【Linux网络#18】:深入理解select多路转接:传统I/O复用的基石
  • ETCD --- lock详解
  • JAVASE(十五)正则表达式
  • 2024年最新版零基础详细Java知识笔记【反射】⑩
  • Python实现 MCP 客户端调用(高德地图 MCP 服务)查询天气工具示例
  • Linux系统