crtp要求严格语法与顺序:base必须为带派生类参数的模板,concrete需在base实例化前完整定义,仅static_cast(this)合法,不支持运行时多态。

CRTP 不是“写对语法就能跑通”的模式,漏掉一个模板实参、错放一个定义顺序,编译器立刻报错或静默 UB——它要求你对模板实例化时机和继承关系有明确控制。
Base 必须显式写出模板实参
常见错误是把 class Concrete : public Base 或 class Concrete : public Base<void></void> 当成合法写法。编译器根本找不到匹配的模板特化,直接报 error: use of class template 'Base' requires template arguments 或更隐蔽的 error: 'Derived' does not name a type。
- 基类
Base必须是模板,且至少带一个类型参数(通常是派生类本身) - 派生类继承时必须完整写出
Base<concrete></concrete>,不能省略、不能用auto、不能传中间基类 - 如果想支持多种派生类共用同一套基类逻辑,得靠模板参数推导(比如通过构造函数)或策略类,而不是在继承链里塞多层 CRTP
派生类定义必须在 Base 实例化前完成
因为 Base<derived></derived> 在实例化时会访问 Derived::impl() 的签名甚至定义(尤其启用 constexpr 或内联时),所以 Derived 不能是 incomplete type。
- 头文件中先定义
Base模板,再定义Concrete类——这是最安全的顺序 - 不能把
Concrete定义放在单独的.cpp文件里然后只在头文件中声明;CRTP 是编译期绑定,所有相关代码必须在同一个翻译单元可见 - 遇到循环依赖?优先用前向声明 + 把实现拆到
inline函数或独立头文件,而不是强行把Base和Concrete套在一起
static_cast(this) 是唯一合法转型方式
有人尝试用 dynamic_cast 或 reinterpret_cast 替代,结果要么编译失败,要么运行时崩溃。CRTP 的安全性完全依赖于编译器对继承关系的静态确认。
-
dynamic_cast要求基类有虚函数,而 CRTP 的前提正是“不带虚函数”,直接触发error: cannot dynamic_cast -
reinterpret_cast绕过类型系统,this的内存布局一旦和Derived不一致(比如多重继承、空基类优化差异),访问impl()就读错偏移,Clang/GCC 开-Wall会警告static_cast不安全,但不会拦reinterpret_cast - 只有
static_cast<derived>(this)</derived>是编译器可验证的:它知道Base<derived></derived>和Derived之间存在公有继承关系,转换合法
CRTP 无法替代虚函数的运行时类型擦除能力
你可以写 std::vector<:unique_ptr>></:unique_ptr> 存不同图形,但没法写 std::vector<base>> ——因为 Base<circle></circle> 和 Base<rect></rect> 是完全不同的类型,不兼容。
- CRTP 是单态(monomorphic):每个特化版本只服务一种派生类,没有统一接口类型
- 真需要运行时混合行为?外层包一层虚接口(如
class Drawable { virtual void draw() = 0; };),让 CRTP 派生类同时继承它;或者改用std::variant<circle rect triangle></circle>+std::visit - 别试图用 CRTP 实现日志器中间层(比如
LoggerBase→FileLoggerBase→FileLogger);CRTP 只认“最末一级派生类”,中间层只会破坏类型一致性
最难绷的是:CRTP 的错误往往不报在出问题的那一行,而是延迟到某个看似无关的调用点才爆发——比如你在 Base 里加了一行 static_cast<t>(this)->validate();</t>,结果编译器翻回去检查 Concrete 是否定义完整,这时才告诉你 incomplete type。写 CRTP,本质是在和模板实例化时机博弈。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











