CRTP是编译期绑定的硬性约束,要求派生类类型在模板实例化时完全可见且调用路径可静态推导;错误前置声明、未声明成员函数、误用is_base_of等均导致编译失败。

CRTP 不是“另一种多态写法”,它是编译期绑定的硬性约束:派生类类型必须在模板实例化时完全可见,且所有调用路径必须能静态推导。用错地方比不用更危险。
class Derived : public Base 必须完整定义后才可继承
常见错误是前置声明 class Derived 后就写 class Derived : public Base<derived></derived>。此时 Base<derived></derived> 尝试实例化,但 Derived 只是声明、未定义,编译器无法计算其大小或成员布局,直接报错(如 invalid use of incomplete type)。
- 正确顺序:先完整定义
Derived类体(含所有成员函数声明),再让它继承Base<derived></derived> - 若需前向引用(如互相依赖),把 CRTP 基类拆成两层:声明-only 的
BaseDecl+ 定义完整的BaseImpl,仅在BaseImpl中做static_cast - 头文件中避免在类定义外提前实例化
Base<derived></derived>—— 比如全局变量、静态成员定义都得放在Derived定义之后
static_cast(this)->foo() 要求 foo() 在 Derived 中已声明
这个转型不是运行时检查,而是编译期契约。一旦 Derived 漏掉 foo(),错误发生在模板实例化阶段,报错位置往往在 Base 内部调用处,信息类似 no member named 'foo' in 'Derived',而不是“虚函数未实现”那种清晰提示。
- 解决办法:在
Base中加static_assert检查接口,例如static_assert(std::is_member_function_pointer_v<decltype>)</decltype> - 别依赖 ADL 或重载解析:CRTP 绑定的是确切函数签名,
foo(int)和foo(double)是两个独立契约,基类必须明确调用哪一个 - const 成员函数要配对:如果
Base::interface()是 const,那static_cast<const derived>(this)->impl()</const>才合法,否则编译失败
std::is_base_of_v , T> 永远为 false
CRTP 生成的 Base<derived></derived> 和 Derived 之间没有传统继承关系语义。它们是独立特化类型,Derived 并非 Base<derived></derived> 的子类(从 OOP 角度),只是语法上写了 public 继承。
立即学习“C++免费学习笔记(深入)”;
- 判断是否用了 CRTP 模式,该用
std::is_convertible_v<t base>*> </t>,而非is_base_of - 泛型算法若依赖基类指针统一处理(如容器存
Base<...>*</...>),必须为每个具体Derived显式构造对应Base<derived>*</derived>,不能靠多态向上转型 - 多个 CRTP 基类混用(如
class D : public Loggable<d>, public Serializable<d></d></d>)时,各自static_cast都安全;但若中间插入一个非模板基类CommonBase,再从它派生D,则static_cast<d>(this)</d>可能越界(因内存布局不连续)
最易被忽略的一点:CRTP 的零开销是有代价的 —— 每个派生类都会触发一次完整的 Base 模板实例化,代码体积随派生类数量线性增长。在资源受限环境(如嵌入式),这比单个虚表指针更占 Flash。是否真需要它,得看 profile 数据,而不是直觉。


















