crtp是编译期多态模式,派生类以模板参数传给基类实现静态调用;不用虚函数因无vtable开销、无需rtti且避免运行时绑定。

CRTP 是什么,为什么不用虚函数
CRTP(Curiously Recurring Template Pattern)本质是把“多态绑定”挪到编译期:派生类把自己作为模板参数传给基类,让基类能静态调用派生类的函数。它不依赖 vtable,没有虚函数调用开销,也不需要运行时对象指针——所以没 dynamic_cast、没 RTTI、也没继承链查找。
典型误用场景:想替代接口抽象但又怕虚函数性能损耗;或者写泛型容器/工具基类时,希望基类直接访问派生类的静态成员或 constexpr 函数。
怎么写一个可用的 CRTP 基类
核心就两步:基类声明为模板,且要求模板参数必须继承自它自己;派生类显式继承 Base<derived></derived>。
常见错误现象:invalid use of incomplete type 或编译器报 “Derived is not a class type”——通常是因为派生类定义还没完成,基类就试图访问其成员。
立即学习“C++免费学习笔记(深入)”;
- 基类中所有对派生类成员的访问,必须放在函数体内部(不能在类体里直接写
Derived::static_member) - 推荐用
static_cast<derived>(this)->foo()</derived>调用派生类函数,而不是依赖 ADL 或非限定名 - 如果派生类有模板参数(比如
Widget<t></t>),CRTP 基类也得是模板模板参数,否则推导失败
示例:
template <typename Derived>
struct Base {
void interface() {
static_cast<Derived*>(this)->impl(); // ✅ 安全调用
}
};
<p>struct MyWidget : Base<MyWidget> { // ✅ 必须显式传入自身
void impl() { }
};CRTP 和普通继承混用会出什么问题
CRTP 基类本身不是多态类型,它没有虚析构函数,也不能被当成基类指针安全持有。一旦你写了 Base<t>* ptr = new MyWidget;</t>,后续 delete ptr 就是未定义行为——因为 Base<t></t> 的析构函数不是 virtual。
使用场景上,CRTP 适合“能力注入”(如 Comparable<t></t>、Cloneable<t></t>),不适合构建运行时可替换的对象层次。若你需要两者共存(比如既有编译期优化,又需要运行时插拔),就得额外加一层虚接口桥接,反而抵消 CRTP 优势。
- 别给 CRTP 基类加虚函数——它已经失去零成本意义
- 别在 CRTP 基类里存
std::unique_ptr<derived></derived>:模板实例化时Derived可能不完整 - 多重 CRTP 继承(如同时继承
Serializable<t></t>和Loggable<t></t>)要小心菱形问题,C++20 的using Base::func可以显式提升,但 C++17 得靠auto func() { return static_cast<derived>(this)->func(); }</derived>手动转发
Clang/GCC 对 CRTP 的诊断差异
Clang 在模板实例化早期就会检查 Derived 是否确实继承自 Base<derived></derived>,而 GCC 可能拖到函数体实例化才报错,导致错误位置偏移、信息模糊。最常踩的坑是忘记在派生类定义末尾加分号,或头文件包含顺序不对,让 Derived 在基类模板中被视为前置声明而非完整类型。
- 加
static_assert(std::is_base_of_v<base>, Derived>)在基类构造函数里,能提前暴露继承关系错误 - 用
-fno-delayed-template-parsing(Clang)或-fdelayed-template-parsing(MSVC)统一解析时机,避免跨编译器行为漂移 - CRTP 类型名太长时,IDE 补全容易卡顿,建议用
using Self = Derived;缩短内部引用
真正难的不是写出来,是判断该不该用——只要涉及运行时类型决策、对象生命周期管理、或需要 std::any/std::function 接口的地方,CRTP 就不该出现。










