CRTP是编译期确定类型组合的架构选择,要求派生类显式传入自身类型作为基类模板参数,所有调用依赖static_cast和静态解析,不支持运行时切换,需用static_assert和if constexpr保障正确性。

CRTP 不是“替代虚函数的技巧”,而是彻底放弃运行时分发的架构选择——它要求你在编译期就决定类型组合,且无法在运行时切换行为。
CRTP 的基类必须显式接收派生类类型作为模板参数
这是整个模式成立的前提。基类不是普通基类,而是一个模板;派生类继承时,必须把自身类型原样传进去,否则 static_cast 会编译失败。
- 错误写法:
class Derived : public Base {}—— 缺少模板实参,Base是不完整类型 - 正确写法:
class Derived : public Base{} - 若派生类带模板参数(如
class Vec3),则需传入完整特化名:class Vec3f : public Base> {} - 基类中所有对派生类成员的调用,都依赖这个静态转换,因此派生类必须提供对应签名的公有成员函数
派生类方法必须与基类调用点严格匹配,否则 SFINAE 不救你
CRTP 没有虚函数表兜底,调用链完全靠编译器静态解析。一旦派生类漏实现、拼错名、参数类型不一致或 const 限定不匹配,错误信息往往指向基类中的 static_cast 行,而非缺失定义的位置。
- 常见报错:
error: invalid static_cast from type 'Base—— 实际原因是*' to type 'Derived*' Derived未定义被调用的函数 - 函数签名差异(比如基类调
draw(),派生类写Draw()或draw() const)会导致链接期“undefined reference”或更隐蔽的模板实例化失败 - 建议在基类接口中加
static_assert检查关键成员是否存在,例如:static_assert(std::is_same_v, "Derived must implement getValue()");
CRTP 与 if constexpr 配合才能处理分支逻辑
纯 CRTP 基类无法根据派生类特性做条件分支——它不知道派生类有没有某个嵌套类型或 constexpr 值。这时候必须靠 if constexpr 在模板函数内部做编译期裁剪。
- 错误思路:在基类构造函数里写
if (Derived::has_optimization)—— 这是运行时判断,且Derived::has_optimization可能根本不可见 - 正确做法:把分支逻辑封装进模板成员函数,用
if constexpr检查表达式有效性,例如:if constexpr (has_member_v) { /* 使用 Derived::value_type */ } - 需要配合
std::is_detected_v或自定义 trait 判断派生类是否提供某类型/函数,否则if constexpr会因硬错误直接终止编译
CRTP 实例化爆炸和 ODR 违规风险真实存在
每个派生类都会实例化一份独立的基类模板副本,看似零开销,但当基类逻辑复杂、包含大量内联函数或静态数据时,代码体积和编译时间会显著上升。更危险的是跨 TU(translation unit)的 ODR 问题。
- 若两个源文件各自定义了
class A : public Base {}和class B : public Base {},而Base中有 inline 函数访问了同一份静态局部变量,则不同 TU 中该变量可能有多份副本,违反 ODR - 避免方式:将基类中所有非模板逻辑抽到非模板辅助类中,或确保所有 inline 函数不含静态局部状态
- 调试时注意:gdb 显示的函数名类似
Base::interface()和Base::interface()是两个完全不同的符号,不能混用
真正难的不是写出第一个 CRTP 示例,而是守住“编译期确定”这条线——任何试图在运行时改变派生类身份、或通过 void* / union / reinterpret_cast 绕过类型系统的行为,都会让整个设计坍塌成不可维护的幻觉。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











