CRTP
CRTP
CRTP = Curiously Recurring Template Pattern。核心形式是:让派生类把自己作为模板参数传给基类
template<typename Derived>
struct Base { /* 使用 static_cast<Derived*>(this) 调用派生接口 */ };
struct Derived : Base<Derived> { /* ... */ };
这是 静态多态(compile-time polymorphism) 的一种实现:基类在编译期就能调用派生类的方法,达成“没有虚函数表、零运行时开销”的多态。
工作原理(Why / How)
基类通过 static_cast<Derived*>(this)
或 static_cast<const Derived*>(this)
把自己转回派生类型,然后直接调用派生实现。因为在最终对象内确实包含派生子对象,所以在合法用法下这是安全的(前提:对象确实是 Derived 的实例)。
典型例子(代码 + 说明)
1) 最简单的 CRTP(基类调用派生实现)
#include <iostream>
template<typename Derived>
struct Base {
void interface() {
// 编译期绑定到 Derived::implementation()
static_cast<Derived*>(this)->implementation();
}
};
struct Derived : Base<Derived> {
void implementation() { std::cout << "Derived::implementation\n"; }
};
int main() {
Derived d;
d.interface(); // 调用 Derived::implementation,且无虚函数开销
}
说明:Base<Derived>
提供公共接口,具体实现由 Derived
提供 —— 静态分派。
2) Mixin:用 CRTP 给派生“混入”操作符
template<typename Derived>
struct EqualityComparable {
friend bool operator==(Derived const& a, Derived const& b) {
return a.equals(b); // 要求 Derived 实现 equals
}
friend bool operator!=(Derived const& a, Derived const& b) {
return !(a == b);
}
};
struct Point : EqualityComparable<Point> {
int x, y;
bool equals(Point const& o) const { return x == o.x && y == o.y; }
};
说明:通过 CRTP 可以把通用操作(operator==/!=)写一次,应用到多个派生类型上(mixin 风格)。
3) 每个派生单独静态计数(每个派生类的静态成员)
template<typename T>
struct Counter {
static int count;
Counter() { ++count; }
Counter(const Counter&) { ++count; }
~Counter() { --count; }
static int live() { return count; }
};
template<typename T> int Counter<T>::count = 0;
struct A : Counter<A> {};
struct B : Counter<B> {};
// A::live() 与 B::live() 各自维护独立计数
说明:因为 Counter<A>
和 Counter<B>
是不同的类型,所以每个派生类都有自己的静态计数器。
用途 / 优点(几点要点)
- 零开销抽象:没有虚表,调用可内联,性能最优。
- mixin / policy-based design:把共通功能以模板混入多个类。
- 静态接口约束:在编译期要求派生实现特定接口(配合 C++20 concepts 更直接)。
- 类型相关静态数据:像上面的计数器,每个派生独立静态变量。
- 常见库实践:许多库(比如 Eigen、Boost.Iterator 等)使用 CRTP 实现可重用/高性能基础设施。
限制 / 注意点(重要)
- 不是运行时多态:
Base<Derived1>
与Base<Derived2>
是不同类型,不能把不同派生放入同一个Base<T>
容器。不能用来替代virtual
用于异构集合或运行时动态绑定。 - 不要通过非多态基类指针删除对象:基类通常无虚析构,若用
Base<Derived>* p = new Derived; delete p;
将未定义行为。通常把基类析构函数设为protected
(防止通过基类指针删除)。 - 对象切片:把
Derived
赋值给Base<Derived>
的值对象会切片(跟普通继承一样)。 - 编译时间/代码膨胀:CRTP 生成多个模板实例,可能增加编译时间和二进制体积。
- 使用错误会导致难懂的模板错误信息:如果派生没有实现被调用的方法,编译器报错通常在基类的调用处。
进阶用法(C++20/概念 & 多重 mixin)
- 使用 C++20
requires
/ concepts 对派生接口进行显式约束:
template<typename D>
concept HasImpl = requires(D& d) { d.impl(); };
template<HasImpl Derived>
struct Base {
void interface() { static_cast<Derived&>(*this).impl(); }
};
这样在编译期会给出更清晰的错误信息(如果 Derived 没实现 impl()
)。
- 多重 mixin:
struct Derived : MixinA<Derived>, MixinB<Derived> { /* ... */ };
可以把很多功能模块化地混入到同一个派生类。
小结(快速要点)
- CRTP 是一种静态多态与 mixin 技术:基类模板以派生类为参数。
- 优点是零运行时开销与强大的复用能力;缺点是不能替代所有虚函数场景、存在模板膨胀和使用注意。
- 常见用途:实现高性能库、迭代器/表达式模板(e.g. Eigen)、policy/mixin 类、按类型分离的静态数据等。
- 建议:当你需要编译期多态、想消除虚函数开销或实现可复用 mixin 时用 CRTP;需要运行时可替换对象或异构容器时用虚函数。