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>→_4BaseI6CircleEBase<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 无法访问 Derived 的 impl()
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
解析:
WidgetA和WidgetB分别继承自不同的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>。
更多推荐




所有评论(0)