CRTP是唯一零开销实现基类调用派生类逻辑的方案,要求基类为模板、派生类显式继承并传入自身完整类型,static_cast(this)是唯一合法转型,且函数签名必须严格匹配。

CRTP 是唯一能在零虚函数、零对象尺寸开销、零调用速度损失下实现“基类调用派生类逻辑”的方案。它不是语法糖,而是编译期契约——写错一个模板实参,编译器立刻报错;漏实现一个函数,错误指向 static_cast<derived>(this)</derived> 行,但根源在派生类缺失定义。
class Derived : public Base 必须写全,不能省略或替换
CRTP 的基类 Base 本身必须是模板,且派生类继承时必须显式传入自身完整类型。这不是可选约定,而是编译器静态解析的硬性前提。
-
class Circle : public Base<circle></circle>✅ 正确:模板参数闭环,Base<circle></circle>可实例化为知道Circle有draw_impl()的具体类型 -
class Circle : public Base❌ 编译失败:Base是模板名,不是类型,报error: use of class template 'Base' requires template arguments -
class Circle : public Base<void></void>❌ 即使能过模板解析,static_cast<void>(this)</void>后无法调用任何成员,后续所有->func()都失败 - 若派生类是模板(如
template<typename t> class Vec3</typename>),继承必须写class Vec3f : public Base<vec3>></vec3>,不能只写Vec3—— 否则Derived不完整,触发incomplete type
static_cast(this) 是唯一合法转型,且调用点签名必须严格匹配
CRTP 安全性的全部依据,就是编译器能在模板实例化时验证 Base<derived></derived> 和 Derived 之间存在公有继承关系。static_cast 在此场景下等价于地址偏移,不依赖 RTTI,也不做运行时检查。
- 禁止用
dynamic_cast:它要求基类带虚函数,直接违背“不带虚函数”前提,编译报cannot dynamic_cast ... because 'Base' is not polymorphic - 禁止用
reinterpret_cast:绕过类型系统,this偏移可能错位,访问Derived::impl()时读到垃圾内存,Clang/GCC 开-Wall会警告但不拦 - 函数签名必须字面一致:基类调
draw_impl() const,派生类写void draw_impl()(非 const)→ 编译失败,错误指向static_cast行,实际缺的是 const 限定 - 返回类型也需协变兼容:建议保持完全相同;若用
auto推导,确保所有派生类实现返回同一类型,否则模板实例化冲突
用 static_assert + if constexpr 主动校验,别等编译器甩长错误
CRTP 没有运行时兜底,错误发生在模板实例化期,但默认报错位置常误导人。把校验前移到基类接口里,能让问题暴露得更早、更准。
立即学习“C++免费学习笔记(深入)”;
- 在基类关键函数中加
static_assert(std::is_same_v<decltype>().getValue()), int>, "Derived must implement getValue() returning int");</decltype>—— 错误直接提示缺失函数及签名要求 - 用
std::is_detected_v自定义 trait 检查函数存在性,比手写decltype(declval...)更健壮,尤其支持重载和 SFINAE 友好 - 分支逻辑必须封装在模板成员函数内,用
if constexpr (has_member_v<derived value_type>)</derived>裁剪;不要在构造函数或普通函数里写if (Derived::flag)—— 这是运行时判断,且Derived::flag可能不可见 - 所有被调用的派生类成员必须为
public;私有/保护继承会导致static_cast失败,因为编译器无法验证公有路径
CRTP 类型不兼容,别试图用裸指针统一持有多种派生类
每个 Base<a></a> 和 Base<b></b> 是完全独立类型,无公共基类,不共享 ABI,也不能隐式转换。这是 CRTP 最容易被忽略的架构边界。
-
std::vector<base>>语法非法 —— C++ 没有模板通配符;std::vector<:unique_ptr> >></:unique_ptr>同样无效 - 想存多个不同 CRTP 实例?只能用
std::variant<a b c></a>+std::visit,或包装成std::any/ 自定义小对象类型擦除器 - 若真需要运行时多态容器,应分层设计:CRTP 实现核心计算逻辑(如
Vec3f::dot()),再让该类同时继承一个带虚函数的轻量接口(如Drawable),桥接两套机制 - 模板膨胀风险真实存在:100 个
Shape派生类 → 100 份Base<shapex></shapex>实例化代码。把稳定逻辑抽成inline工具函数,CRTP 基类只留调度胶水
CRTP 的复杂点不在语法,而在整个类型决策必须前移到模板实例化阶段:派生类定义顺序、头文件包含关系、函数签名一致性、以及是否真的需要运行时异构集合——这些都不是编译器能帮你兜底的事。


















