CRTP的基本写法是派生类以自身为模板参数继承模板基类,即class Derived : public Base,基类通过static_cast(this)在编译期调用派生类成员,实现零开销静态多态。

CRTP 的基本写法长什么样
CRTP 不是语法糖,它靠的是模板参数显式传递派生类类型,让基类在编译期就知道“谁在继承我”。最简结构就是:Base<derived></derived> 这种继承形式,且 Base 内部用 static_cast<derived>(this)</derived> 调用子类方法。
常见错误是忘记把派生类名作为模板实参传给基类,比如写成 class Concrete : public Base 或 class Concrete : public Base —— 编译器会直接报错找不到匹配的模板特化。
-
Base必须是模板类,且至少接受一个类型参数(通常是Derived) - 派生类定义时必须显式写出
Base<concrete></concrete>,不能省略模板实参 -
static_cast<derived>(this)</derived>是安全的,但前提是继承关系严格成立;若误用于多重继承或不相关类型,会触发未定义行为
为什么不能用 dynamic_cast 或 reinterpret_cast 替代 static_cast
dynamic_cast 依赖 RTTI 和虚函数表,而 CRTP 的前提正是“不用虚函数”,所以它根本不可用;reinterpret_cast 绕过类型系统,把 this 强转为任意指针,编译期不检查,运行时极易崩溃。
只有 static_cast 在这种场景下是合法且可验证的:编译器知道 Base<derived></derived> 和 Derived 之间的继承关系,能确认转换有效。这也是 CRTP 被称为“静态”多态的关键依据——类型关系在模板实例化时就已固化。
立即学习“C++免费学习笔记(深入)”;
- 用
dynamic_cast会导致编译失败(基类无虚函数,不满足使用条件) - 用
reinterpret_cast可能通过编译,但访问Derived::implementation()时大概率读到错误内存偏移 - Clang/GCC 在开启
-Wall时会对非法static_cast给出明确提示,利于早期发现问题
CRTP 和普通模板函数多态的区别在哪
CRTP 是“带继承结构的静态多态”,重点在统一接口 + 层级复用;而函数模板(如 template<typename t> void render(const T&)</typename>)是“无约束的鸭子类型”,只要 T 有 .draw() 就能用,不要求继承关系。
这意味着:CRTP 更适合需要共享状态、强制接口一致性、或需在基类中实现通用逻辑(如计数、日志、缓存)的场景;函数模板更轻量,但无法在调用前对类型做任何“基类层面”的干预。
- CRTP 实例化后,每个派生类对应一个独立的基类特化版本,可能增大代码体积
- 函数模板对每个实参类型也生成一份代码,但无继承开销,也不要求类型间有关系
- CRTP 支持基类中定义非虚的公共成员函数(如
validate()),并由所有派生类隐式继承;函数模板做不到这点
哪些地方最容易漏掉 CRTP 的约束条件
CRTP 看似简单,但实际落地时几个细节常被忽略,导致行为异常或编译失败:
- 派生类必须在定义完成后再实例化基类模板,否则
static_cast<derived>(this)</derived>中的Derived是不完整类型,GCC/Clang 会拒绝编译 - 如果基类模板里调用了派生类的
constexpr成员(如Derived::version),该成员必须在派生类定义体内声明,且不能依赖自身未定义的部分 - CRTP 基类不能有虚析构函数——加了就违背“零虚表”初衷;若真需要多态销毁,说明你其实该用虚函数,CRTP 不适用
真正难的不是写出来,而是想清楚:这个抽象是否真的需要继承层级?有没有更简单的模板函数或概念约束(C++20 concept)就能解决?CRTP 是利器,但不是默认选项。


















