CRTP是一种编译期确定的模板惯用法:派生类以自身为模板参数继承基类,实现零开销静态多态;它支持编译期类型检查、完全内联,但不支持动态多态或运行时行为。

CRTP 是什么:编译期确定的“假继承”
CRTP(Curiously Recurring Template Pattern)不是语言特性,而是一种模板编程惯用法:派生类以自身为模板参数继承基类。它让基类在编译期就能“知道”最终派生类型,从而实现静态多态——不靠 vtable、不产生虚函数调用开销。
典型写法长这样:
template <typename Derived>
class Base {
public:
void interface() {
static_cast<Derived*>(this)->implementation(); // 编译期绑定
}
};
<p>class MyConcrete : public Base<MyConcrete> {
public:
void implementation() { /<em> 实际逻辑 </em>/ }
};为什么用 CRTP 而不用虚函数
核心动机是零成本抽象:避免运行时虚函数查表、禁止对象切片、支持 static_assert 在编译期校验接口契约。
- 性能敏感场景(如数学库、嵌入式驱动)中,
Base<T>::interface()可被完全内联,生成和直接调用MyConcrete::implementation()几乎等价的汇编 - 基类可强制要求派生类提供特定成员(比如
value_type或size()),用static_cast<Derived*>(this)->size()触发 SFINAE 或编译错误 - 无法用于多态数组或动态向上转型——CRTP 对象之间没有公共基类指针/引用关系,
Base<A>*和Base<B>*类型完全不同
容易踩的坑:循环依赖与 this 指针安全
CRTP 最隐蔽的问题不是语法,而是派生类定义未完成时基类就试图访问其成员。
立即学习“C++免费学习笔记(深入)”;
- 在
Base<Derived>构造函数里调用static_cast<Derived*>(this)->xxx()是未定义行为——此时Derived的构造函数还没开始执行,对象内存未就绪 - 若基类模板中用到
Derived::some_nested_type,必须确保该类型在继承声明前已定义(常需前置声明 + 后续定义分离) - 不要试图在 CRTP 基类中存储
Derived*并长期持有——生命周期管理责任不清,易悬垂
典型用途:表达式模板与混合类型操作
CRTP 真正发挥威力的地方,是需要在编译期组合类型行为的场景,比如 Eigen、xtensor 这类数值库。
例如实现向量加法不立即计算,而是构建表达式树:
template <typename LHS, typename RHS>
class VectorAdd : public Base<VectorAdd<LHS,RHS>> {
LHS lhs_;
RHS rhs_;
public:
auto operator[](size_t i) const { return lhs_[i] + rhs_[i]; }
};
<p>// 用户代码:
auto expr = vec1 + vec2; // 类型是 VectorAdd<Vec, Vec>,无临时对象、无虚调用
double x = expr[5]; // 到这里才真正计算这种模式依赖 CRTP 让 Base 能统一处理所有表达式类型,同时保持每个具体表达式类型可区分、可优化。一旦涉及运行时决定行为(比如用户输入算子类型),CRTP 就不再适用——它只活在编译期。










