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

CRTP(Curiously Recurring Template Pattern)


C++ 中的 CRTP(奇异递归模板模式)

CRTP(Curiously Recurring Template Pattern)是一种利用模板继承实现 静态多态(Static Polymorphism) 的设计模式。通过基类模板以派生类作为模板参数,CRTP 允许在编译期实现多态行为,避免虚函数开销,同时提供灵活的类型操作。以下通过代码和底层原理全面解析其实现和应用。


1. CRTP 的基本结构
1.1 核心思想
  • 基类模板:接受派生类作为模板参数。
  • 派生类:继承自以自身为模板参数的基类模板。
// 基类模板
template <typename Derived>
class Base {
public:// 通过静态转换访问派生类Derived& derived() { return static_cast<Derived&>(*this); }
};// 派生类继承自以自身为模板参数的基类
class MyDerived : public Base<MyDerived> {// 派生类的实现
};

2. CRTP 的典型应用场景
2.1 静态多态(替代虚函数)
template <typename Derived>
class Shape {
public:void draw() {// 调用派生类的具体实现static_cast<Derived*>(this)->drawImpl();}
};class Circle : public Shape<Circle> {
public:void drawImpl() { std::cout << "Drawing Circle" << std::endl; }
};class Square : public Shape<Square> {
public:void drawImpl() { std::cout << "Drawing Square" << std::endl; }
};int main() {Circle circle;Square square;circle.draw();  // 输出 "Drawing Circle"square.draw();  // 输出 "Drawing Square"
}

底层原理

  • 通过 static_cast 在编译期将基类指针转换为派生类指针,直接调用具体实现。
  • 无虚函数表(vtable)开销,性能更高。

2.2 接口扩展与代码复用
template <typename Derived>
class Addable {
public:Derived operator+(const Derived& other) const {Derived result = static_cast<const Derived&>(*this);result += other;return result;}
};class Vector : public Addable<Vector> {
public:int x, y;Vector(int x, int y) : x(x), y(y) {}Vector& operator+=(const Vector& other) {x += other.x;y += other.y;return *this;}
};int main() {Vector v1(1, 2), v2(3, 4);Vector v3 = v1 + v2;  // 使用基类的 operator+ 实现std::cout << v3.x << ", " << v3.y << std::endl;  // 输出 "4, 6"
}

2.3 对象计数(编译期统计)
template <typename Derived>
class ObjectCounter {
public:static int count;ObjectCounter() { count++; }~ObjectCounter() { count--; }
};template <typename Derived>
int ObjectCounter<Derived>::count = 0;class MyClass : public ObjectCounter<MyClass> {};int main() {MyClass a, b;std::cout << MyClass::count << std::endl;  // 输出 "2"{MyClass c;std::cout << MyClass::count << std::endl;  // 输出 "3"}std::cout << MyClass::count << std::endl;  // 输出 "2"
}

3. CRTP 的底层原理
3.1 模板实例化与类型推导
  • 基类模板参数:在派生类定义时,将自身类型传递给基类模板。
  • 静态类型转换:通过 static_cast 在编译期完成派生类类型的解析。
3.2 符号生成与名称修饰
  • 每个派生类实例化基类模板时,生成独立的符号。
  • 例如:
    • Base<Circle>_4BaseI6CircleE
    • Base<Square>_4BaseI6SquareE

4. CRTP 的优缺点
优点缺点
无虚函数开销,性能更高代码可读性较低,设计复杂度较高
编译期多态,避免运行时类型检查派生类需明确知晓基类模板的实现细节
灵活的类型操作(如运算符重载、静态接口扩展)模板错误信息可能难以调试

5. CRTP 的高级应用
5.1 链式调用(Fluent Interface)
template <typename Derived>
class Chainable {
public:Derived& self() { return static_cast<Derived&>(*this); }Derived& setName(const std::string& name) {self().name = name;return self();}Derived& setValue(int value) {self().value = value;return self();}
};class Config : public Chainable<Config> {
public:std::string name;int value;
};int main() {Config config;config.setName("MyApp").setValue(42);std::cout << config.name << " " << config.value << std::endl; // 输出 "MyApp 42"
}

5.2 编译期策略模式
template <typename Derived>
class SortingPolicy {
public:void sort(int* data, size_t size) {static_cast<Derived*>(this)->sortImpl(data, size);}
};class QuickSort : public SortingPolicy<QuickSort> {
public:void sortImpl(int* data, size_t size) {std::cout << "QuickSort" << std::endl;// 具体实现}
};class MergeSort : public SortingPolicy<MergeSort> {
public:void sortImpl(int* data, size_t size) {std::cout << "MergeSort" << std::endl;// 具体实现}
};int main() {int arr[5] = {5, 3, 1, 4, 2};QuickSort sorter;sorter.sort(arr, 5);  // 输出 "QuickSort"
}

6. 总结
场景技术要点
静态多态通过 static_cast 调用派生类方法,避免虚函数开销
接口扩展基类模板提供通用操作(如 operator+),派生类实现具体逻辑(如 operator+=
编译期对象计数利用静态成员变量统计实例数量
链式调用返回派生类引用实现链式操作
策略模式基类定义策略接口,派生类实现具体策略

CRTP 通过模板继承和编译期类型转换,将多态行为提前到编译期处理,适用于高性能、低延迟场景。合理使用可提升代码复用性和灵活性,但需注意设计复杂度和可维护性。


多选题


题目 1:CRTP 基类如何访问派生类的方法?

以下代码的输出是什么?

template <typename Derived>
class Base {
public:void execute() {static_cast<Derived*>(this)->impl();}
};class Derived : public Base<Derived> {
public:void impl() { std::cout << "Derived impl" << std::endl; }
};int main() {Derived d;d.execute();return 0;
}

A. 输出 Derived impl
B. 编译失败,Base 无法访问 Derivedimpl()
C. 运行时错误,类型转换失败
D. 输出未定义行为


题目 2:静态多态与动态多态的性能对比

以下关于 CRTP 的说法,哪一项正确?

A. CRTP 通过虚函数表实现多态,性能与动态多态相同
B. CRTP 在编译期解析方法调用,性能优于动态多态
C. CRTP 必须在运行时通过 RTTI 检查类型
D. CRTP 的性能不如动态多态,因为模板实例化开销大


题目 3:CRTP 实现对象计数的行为

以下代码的输出是什么?

template <typename Derived>
class Counter {
public:static int count;Counter() { count++; }~Counter() { count--; }
};template <typename Derived>
int Counter<Derived>::count = 0;class WidgetA : public Counter<WidgetA> {};
class WidgetB : public Counter<WidgetB> {};int main() {WidgetA a1, a2;WidgetB b1;std::cout << WidgetA::count << " " << WidgetB::count << std::endl;return 0;
}

A. 2 1
B. 3 1
C. 2 0
D. 1 1


题目 4:CRTP 链式调用的实现

以下代码是否能编译通过?

template <typename Derived>
class Chainable {
public:Derived& setX(int x) {static_cast<Derived*>(this)->x = x;return *static_cast<Derived*>(this);}
};class Point : public Chainable<Point> {
public:int x, y;Point& setY(int y) { this->y = y; return *this; }
};int main() {Point p;p.setX(10).setY(20);return 0;
}

A. 编译成功
B. 编译失败,setX 返回类型错误
C. 编译失败,Point 未继承 Chainable 的正确版本
D. 运行时错误


题目 5:CRTP 与模板特化的交互

以下代码的输出是什么?

template <typename Derived>
class Base {
public:void print() { std::cout << "Base" << std::endl; }
};template <>
class Base<int> {
public:void print() { std::cout << "Base<int>" << std::endl; }
};class Derived : public Base<Derived> {
public:void print() { std::cout << "Derived" << std::endl; }
};int main() {Derived d;d.print();return 0;
}

A. 输出 Base
B. 输出 Derived
C. 输出 Base<int>
D. 编译失败,Derived 无法继承 Base<Derived>



答案与解析


题目 1:CRTP 基类如何访问派生类的方法?

答案:A
解析

  • CRTP 的基类通过 static_cast<Derived*>(this)this 指针转换为派生类指针,直接调用 impl()
  • 选项 B 错误,因为 CRTP 基类与派生类是模板继承关系,编译期即可解析方法调用。

题目 2:静态多态与动态多态的性能对比

答案:B
解析

  • CRTP 的静态多态在编译期完成方法绑定,无需虚函数表查找,性能更高。
  • 选项 A 错误,CRTP 不依赖虚函数表;选项 C 和 D 均与 CRTP 的机制矛盾。

题目 3:CRTP 实现对象计数的行为

答案:A
解析

  • WidgetAWidgetB 分别继承自不同的 Counter 实例化模板,因此它们的静态成员 count 是独立的。
  • WidgetA 构造两次,count 为 2;WidgetB 构造一次,count 为 1。

题目 4:CRTP 链式调用的实现

答案:A
解析

  • Chainable::setX 返回 Derived&(即 Point&),因此 p.setX(10) 返回 Point 对象,支持链式调用 setY(20)
  • 选项 B 错误,返回类型正确;选项 C 和 D 无依据。

题目 5:CRTP 与模板特化的交互

答案:B
解析

  • Derived 继承自 Base<Derived>,未使用 Base<int> 的特化版本。
  • Derived::print() 隐藏了基类的 print(),因此直接调用派生类方法。
  • 选项 C 错误,Derived 的基类是 Base<Derived>,而非 Base<int>
http://www.xdnf.cn/news/119557.html

相关文章:

  • 试水低代码平台Nocoly
  • 基于Matlab的车牌识别系统
  • 倚光科技:详解非球面光学元件的加工与检测方法
  • DrissionPage 请求一次换一个代理(不重启chrome)
  • 【MongoDB + Spark】 技术问题汇总与解决方案笔记
  • FastMCP与FastAPI:构建自定义MCP服务器
  • 架构-信息安全技术基础知识
  • 基于Python+Flask的MCP SDK响应式文档展示系统设计与实现
  • SpringSecurity源码解读AbstractAuthenticationProcessingFilter
  • 沁恒CHV203中断嵌套导致修改线程栈-韦东山
  • 使用 VMware 安装一台 Linux 系统之Centos
  • 国芯思辰| 24位生理电采集模拟前端100%兼容ADS1294R睡眠监测仪
  • 济南国网数字化培训班学习笔记-第二组-3节-电网工程建设项目部门
  • VLM模型评估
  • 扣子空间出版的扣子空间使用手册和介绍
  • 数据库+Docker+SSH三合一!深度评测HexHub的全栈开发体验
  • R语言中的常用内置函数
  • Spring Boot常用注解详解:实例与核心概念
  • 各种各样的bug合集
  • HTML给图片居中
  • FreeRTOS【3】任务调度算法
  • Qt —— 在Linux下试用QWebEngingView出现的Js错误问题解决(附上四种解决办法)
  • React 与 Vue:两大前端框架的深度对比
  • 4月份最新---Meta发明了一种很新的Transformer
  • 【AI】基于OllamaSharp与.NET Core API的高效LLM查询实现
  • Langchain_Agent+数据库
  • 从对数变换到深度框架:逻辑回归与交叉熵的数学原理及PyTorch实战
  • ssh启动不了报错
  • 3台CentOS虚拟机部署 StarRocks 1 FE+ 3 BE集群
  • React19源码阅读之commitRoot