crtp是编译期实现静态多态的模板模式,基类通过模板参数获知派生类类型并直接调用其成员函数,避免虚函数开销但丧失运行时灵活性。

CRTP 是什么:编译期确定的“虚函数”替代方案
CRTP 不是运行时多态,它压根不涉及 vtable 或指针间接调用;它是通过模板参数让基类在编译期“知道”派生类类型,从而直接调用派生类的静态成员函数。本质是把本该动态分发的逻辑,提前到模板实例化阶段做静态绑定。
典型写法长这样:
template <typename Derived>
struct Base {
void interface() { static_cast<Derived*>(this)->impl(); }
};
<p>struct MyImpl : Base<MyImpl> {
void impl() { /<em> 实际逻辑 </em>/ }
};
为什么非要用 static_cast<derived>(this)</derived> 而不是 dynamic_cast 或普通转型
因为 Derived 是模板参数,编译器完全清楚它的完整类型,且 Base<derived></derived> 只会被 Derived 自己继承 —— 这个转型 100% 安全,dynamic_cast 不仅多余,还要求 RTTI 开启,且无法用于非多态类型。
-
static_cast在这里不是“冒险”,而是利用了 CRTP 的契约:派生类必须继承自Base - 如果误写成
Base<other></other>,编译器会在static_cast处报错,错误信息通常是invalid static_cast from 'Base<other>*' to 'Other*'</other> - 别用
reinterpret_cast:它绕过类型系统,一旦继承关系稍有变化(比如加虚函数、多重继承),行为就未定义
CRTP 和普通虚函数比,性能和限制在哪
优势很实在:零开销 —— 没有虚表查找、没有指针解引用、函数还能被内联。但代价是灵活性归零:
- 不能在运行时决定调用哪个实现,
Base<a></a>和Base<b></b>是两个完全无关的类型,无法放进同一容器(除非用类型擦除包装) - 派生类必须在定义时就明确继承自
Base,没法后期“注入”行为 - 调试时栈帧里看不到“多态跳转”,只有层层模板展开,gdb 里可能显示为
Base<myimpl>::interface()</myimpl>,容易误以为没进派生类 - 如果派生类的
impl()是私有的,Base无法访问 —— 得加friend class Derived;或把函数设为公有/受保护
常见踩坑:模板参数名、SFINAE 和构造顺序问题
最隐蔽的坑往往不在模式本身,而在配套代码细节:
立即学习“C++免费学习笔记(深入)”;
- 别把模板参数起名叫
T或U—— 写成Derived是强约定,否则看static_cast<t>(this)</t>根本看不出意图,也容易和其它模板混淆 - 如果想让
Base支持部分特化(比如只对有value_type的类型启用某接口),得靠std::enable_if或 C++20requires,光靠函数重载会因 SFINAE 失败而静默不生效 - 在
Base构造函数里调用static_cast<derived>(this)->impl()</derived>是未定义行为 —— 此时Derived对象还没开始构造,内存布局都未就绪 - CRTP 基类里的成员变量,和派生类成员变量在内存中是连续排布的,但别依赖这个顺序做指针偏移计算 —— 编译器可能插入填充字节,尤其当两者有不同对齐要求时
真正难的从来不是写出第一个 CRTP 示例,而是判断某个需求到底该用 CRTP、虚函数、还是策略模板 —— 类型是否固定?是否需要运行时切换?有没有反射或序列化需求?这些才是卡住人的地方。










