C++中的多态分为动态多态和静态多态两种形式,动态多态通过虚函数表实现运行时绑定,而静态多态则可以在编译期确定调用逻辑,CRTP模式就是实现静态多态的经典方案。CRTP全称是奇异递归模板模式,其核心思路是让派生类继承自一个以自身为模板参数的基类模板,从而在基类中通过模板参数访问派生类的成员。

CRTP模式的基本实现结构
CRTP的实现需要满足两个核心条件,一是基类是一个模板类,二是派生类继承基类时,将自身作为模板参数传递给基类。下面是一个最基础的CRTP示例:
#include <iostream>
// 基类模板,模板参数为派生类类型
template <typename Derived>
class Base {
public:
void interface() {
// 调用派生类的实现方法
static_cast<Derived*>(this)->implementation();
}
};
// 派生类,继承时传入自身作为模板参数
class Derived1 : public Base<Derived1> {
public:
void implementation() {
std::cout << "Derived1的实现逻辑" << std::endl;
}
};
// 另一个派生类
class Derived2 : public Base<Derived2> {
public:
void implementation() {
std::cout << "Derived2的实现逻辑" << std::endl;
}
};
int main() {
Derived1 d1;
Derived2 d2;
d1.interface(); // 输出:Derived1的实现逻辑
d2.interface(); // 输出:Derived2的实现逻辑
return 0;
}
静态多态的核心原理
上述示例中,基类Base的interface方法里通过static_cast将this指针转换为派生类类型,然后调用派生类的implementation方法。这个过程在编译期就已经确定,编译器会根据派生类的具体类型生成对应的调用代码,不需要运行时查询虚函数表,因此属于静态多态。
和普通动态多态对比,CRTP实现的静态多态有以下特点:
- 没有虚函数表的开销,调用效率更高
- 多态逻辑在编译期确定,无法进行运行时的类型替换
- 不需要虚函数,派生类可以不重写基类方法,但需要保证调用的成员存在
CRTP的常见应用场景
1. 公共逻辑的复用
当多个派生类有相似的前置或后置处理逻辑时,可以将公共逻辑放在基类中,派生类只需要实现差异化的部分。例如下面的计数器场景:
#include <iostream>
template <typename Derived>
class Counter {
public:
void increment() {
// 公共的前置逻辑
std::cout << "准备增加计数" << std::endl;
// 调用派生类的计数增加实现
static_cast<Derived*>(this)->do_increment();
// 公共的后置逻辑
std::cout << "计数增加完成" << std::endl;
}
};
class MyCounter : public Counter<MyCounter> {
public:
MyCounter() : count(0) {}
void do_increment() {
count++;
std::cout << "当前计数:" << count << std::endl;
}
private:
int count;
};
int main() {
MyCounter counter;
counter.increment();
return 0;
}
2. 编译期接口约束
CRTP可以在编译期检查派生类是否满足特定的接口要求,如果派生类没有实现基类需要调用的方法,编译时就会报错,避免运行时错误。例如下面的示例要求派生类必须实现draw方法:
#include <iostream>
template <typename Derived>
class Drawable {
public:
void draw() {
static_cast<Derived*>(this)->do_draw();
}
};
class Circle : public Drawable<Circle> {
public:
void do_draw() {
std::cout << "绘制圆形" << std::endl;
}
};
// 如果派生类没有实现do_draw方法,编译时会报错
// class Rectangle : public Drawable<Rectangle> {};
int main() {
Circle c;
c.draw(); // 输出:绘制圆形
return 0;
}
使用CRTP的注意事项
虽然CRTP有性能优势,但使用时也需要注意以下问题:
- CRTP会导致代码膨胀,因为每个派生类都会实例化一份基类模板的代码,如果派生类数量很多,会增加编译后的二进制体积
- 不支持运行时的类型替换,无法通过基类的指针或引用统一管理不同派生类的对象,只能使用具体的派生类类型
- 基类和派生类的耦合度较高,派生类的实现需要符合基类的调用预期,修改基类可能会影响所有派生类
CRTP与动态多态的选择
在实际开发中,选择CRTP还是动态多态可以根据需求判断:如果需要运行时灵活替换对象类型,优先选择虚函数实现的动态多态;如果对性能要求高,且类型在编译期就能确定,CRTP是更好的选择。两者并非互斥,也可以结合使用,在需要静态绑定的场景用CRTP,需要动态绑定的场景用虚函数。