crtp是唯一零开销实现基类调用派生类逻辑的方案,依赖编译期严格契约:base必须为模板、派生类显式继承base、函数签名须逐字符一致、static_assert和if constexpr需谨慎使用,且类型间不兼容。

CRTP 是唯一能在零虚函数、零对象尺寸开销、零调用速度损失下,让基类调用派生类逻辑的方案。它不是“写对模板就能跑”,而是编译期契约——错一个模板实参、漏一个函数签名、多一层继承,编译器立刻报错,且错误常指向 static_cast<derived>(this)</derived> 那一行,但真正问题在派生类定义里。
Base 必须显式写出,不能省略或替换
基类 Base 本身是模板,不是类型;Base<derived></derived> 才是可实例化的具体类型。编译器靠这个模板实参确认继承关系和内存布局,从而允许安全的 static_cast。
-
class Circle : public Base<circle></circle>✅ 正确:闭环明确,Base<circle></circle>知道Circle有draw_impl() -
class Circle : public Base❌ 编译失败:Base是模板名,不是类型,报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
派生类函数签名必须与基类调用点字面一致
CRTP 不做名称查找适配、不隐式转换、不重载解析。static_cast<derived>(this)->draw_impl() const</derived> 这一行能否通过,完全取决于 Derived 是否存在**逐字符相同、参数类型/个数/const 限定符/引用类别完全一致、返回类型协变兼容**的公有成员函数。
- 拼写差一个字母(
draw_implvsDrawImpl)→ 编译失败,错误指向static_cast行 - 基类调
void draw_impl() const,派生类只写void draw_impl()(非 const)→ 匹配失败 - 参数类型不一致:基类传
int,派生类声明为long→ 不会隐式转换,直接找不到函数 - 建议在基类关键接口中加
static_assert主动校验,例如:static_assert(std::is_same_v<decltype>().get_value()), int>)</decltype>
用 if constexpr 替代运行时分支,所有分支都必须可实例化
当不同派生类需差异化行为时,不能用 typeid 或虚函数分发,而应在模板成员函数内用 if constexpr 做编译期裁剪。但它不是“只编译选中的分支”——未选中分支里的符号仍需语法正确、可解析。
- 正确写法:
if constexpr (std::is_same_v<derived concretea>) { static_cast<derived>(this)->special_opt(); }</derived></derived> - 错误写法:在构造函数或普通非模板函数里写
if constexpr→ 它无法捕获Derived类型信息 - 每个分支内调用的函数(如
special_opt())必须已在对应派生类中明确定义;否则即使该分支未被选中,也会导致硬错误 - 配合
std::is_detected_v自定义 trait 检查函数是否存在,比裸写if constexpr更健壮
CRTP 类型天然不兼容,别试图用裸指针统一持有
Base<circle>*</circle> 和 Base<square>*</square> 是两个完全无关的类型,没有公共基类,也不能隐式转换。C++ 没有 “Base” 这种通配语法。
-
std::vector<base>*> v;❌ 语法非法 -
Base*作为函数参数或返回类型 ❌ 不合法 - 想统一持有多种 CRTP 对象?只能用
std::variant<circle square></circle>+std::visit,或包装成std::any,或手写小对象类型擦除 - 多重继承或虚继承会破坏
static_cast<derived>(this)</derived>的地址偏移假设 → 直接禁用,CRTP 只接受单一、公有、非虚继承
最常被忽略的是:CRTP 错误总在模板实例化期爆发,但报错位置常误导人;你得习惯顺着 static_cast<derived>(this)</derived> 往下查,最终定位到派生类是否漏实现、拼错名、或 const 不匹配——而不是怀疑基类写错了。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











