CRTP是编译期绑定的严格契约,要求派生类必须显式、完整地作为模板参数传入基类,并精确实现基类调用的函数签名,否则立即编译失败。

CRTP 不是“不用虚函数就能跑通的技巧”,它是编译期绑定的契约——写错模板参数、漏实现函数、传错类型,编译器立刻报错,但错误位置常在基类调用行,而非缺失定义处。
CRTP 基类必须显式接收派生类类型作为模板参数
这是整个模式成立的硬性前提。BaseDerived 必须是最终派生类的**完整、具名、非推导类型**。
- 错误写法:
class Circle : public ShapeBase {}→ShapeBase是未实例化的模板,编译失败 - 错误写法:
class Circle : public ShapeBase或class Circle : public ShapeBase→static_cast无效,或导致未定义行为(this) - 正确写法:
class Circle : public ShapeBase→ 模板参数闭环,static_cast才有定义(this) - 若派生类带模板参数(如
Vec3f),必须传入特化名:class Vec3f : public ShapeBase
派生类必须严格匹配基类调用点的函数签名
CRTP 不做名称查找容错,不支持重载解析、const 适配或返回类型协变。调用链完全静态,任何偏差都直接中断编译。
-
Base调用::draw() static_cast,则(this)->draw_impl() D必须存在 公有、非模板、非重载、签名完全一致 的draw_impl() -
void draw_impl() const和void draw_impl()是两个函数;基类调非 const 版本时,只提供 const 版本会编译失败 - 参数类型必须精确匹配:比如基类调
process(int),派生类写process(long)或process(int, int = 0)都不行 - 建议在基类中加
static_assert校验关键成员,例如:static_assert(std::is_same_v().getValue()), int>, "Derived::getValue() must return int");
CRTP 无法用于统一持有多种派生类对象
每个 Base 和 Base 是完全不同的类型,没有公共基类,不能放进同一容器或用同一指针类型指向。
-
std::vector语法非法 —— C++ 没有“模板通配符”> -
ShapeBase和* ShapeBase类型不兼容,不能赋值给同一变量* - 若真需运行时混合操作,只能组合方案:
– 内部用 CRTP 实现零开销核心逻辑(如向量运算、树遍历)
– 外层再继承一个虚基类(如Drawable { virtual void draw() = 0; }),由 CRTP 派生类同时继承它
– 或改用std::variant+std::visit
CRTP 的 static_cast 安全性依赖标准对象布局
static_cast 不是运行时类型检查,而是基于内存偏移的 reinterpret —— 它假设派生类对象起始地址与基类子对象一致。
- 只要不使用虚继承、多重非首继承,该转换就是定义良好的(ISO C++ [class.derived] / 10.2)
- 基类构造函数中调用
static_cast是危险的:此时(this)->init() Derived成员尚未构造,即使指针有效,访问其成员仍是未定义行为 - 不要在基类构造/析构期间调用派生类函数;所有 CRTP 调用应发生在对象完全构造之后
- 若需在构造阶段注入行为,改用模板参数策略(如
template),而非 CRTPclass Base
最容易被忽略的是:CRTP 的错误不是“运行时崩溃”,而是“编译失败位置反直觉”。你改了派生类,却在基类某行 static_cast 上收到“no member named 'xxx'”——那说明你漏了实现,或者拼错了名、漏了 const、参数类型不对。它不兜底,也不迁就,只认契约。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











