CRTP是唯一零开销实现基类调用派生类逻辑的方案,但需严格遵守编译期契约:模板实参不可省略、继承须public单继承、函数签名字面一致、成员public且定义完整,否则编译失败。

CRTP 是唯一能在零虚函数、零对象尺寸膨胀、零调用开销前提下,让基类调用派生类逻辑的方案;但它不是“模板写对就跑通”的技巧,而是一套编译期契约——漏一个 const、传错一个模板实参、多加一个基类,编译器立刻报错,且错误常卡在 static_cast<derived>(this)->func()</derived> 这一行,真正问题却藏在派生类定义里。
为什么 class Derived : public Base 一定编译失败
因为 Base 是模板名,不是类型。编译器需要具体特化才能生成代码,比如 Base<circle></circle> 或 Base<vec3>></vec3>。漏掉模板参数等于没给钥匙,连门都打不开。
-
class Circle : public Base→ 报错:use of class template 'Base' requires template arguments -
class Circle : public Base<void></void>→static_cast<void>(this)</void>后无法访问任何成员,后续所有->draw_impl()都失败 -
class Vec3f : public Base<vec3></vec3>→ 错误:Vec3是模板,不是类型;必须写Base<vec3>></vec3> - 若派生类仅前向声明(
class Circle;),未完成定义,实例化Base<circle></circle>时触发incomplete type错误
为什么 static_cast<derived>(this)->func()</derived> 突然失效
这个转换本身不崩溃,但合法的前提是:派生类必须以 public 方式单继承该 CRTP 基类,且不能有虚继承或多继承干扰内存布局。一旦破坏,static_cast 就变成未定义行为——Clang/GCC 开 -Wall 可能警告,但不拦你编译通过。
- 私有/保护继承 → 编译失败:
conversion from 'Base*' to 'Derived*' is inaccessible - 多重继承(如
class D : public Base<d>, public Other</d>)→this指针偏移错位,->func()可能读到其他基类内存 - 构造函数体内调用
static_cast<derived>(this)->func()</derived>→ 危险!此时派生类成员尚未构造,哪怕只读地址也属未定义行为 - 绝不能用
dynamic_cast替代:报错cannot dynamic_cast 'this' because 'Base' is not polymorphic
如何让编译错误指向真正缺失的地方
默认情况下,错误停在 static_cast<derived>(this)->draw_impl()</derived>,但真正问题是 Derived 根本没定义这个函数。靠 static_assert 提前拦截,把错误拉回基类定义处,避免排查绕弯。
立即学习“C++免费学习笔记(深入)”;
- 在基类接口中加校验:
static_assert(std::is_same_v<decltype>().getValue()), int>, "Derived::getValue() must return int");</decltype> - 用
std::is_detected_v自定义 trait 检查函数存在性与签名:has_draw_impl_v<derived></derived> - 所有被调用的派生类成员必须为
public,且不能依赖未完成定义——派生类定义必须在基类模板实例化前完成 - 拼错名(
draw_implvsdrawImpl)、const不一致、参数类型差一个int/long,都会导致编译失败,且错误不提示“你少写了一个 const”
为什么 std::vector<base>> 无法统一持有多个派生类对象
Base<circle></circle> 和 Base<square></square> 是两个完全无关的类型,没有公共基类,也不满足任何隐式转换关系。C++ 没有“模板通配符”,std::vector<base>> 语法非法。
-
std::vector<:unique_ptr>>></:unique_ptr>无法存Base<square>*</square>,类型不兼容 -
auto不能用于模板实参推导(C++20 也不支持) - 真需运行时混合操作,只能组合方案:内层用 CRTP 实现零开销核心逻辑(如向量运算),外层再继承一个虚基类(如
Drawable { virtual void draw() = 0; }),由 CRTP 派生类同时继承它 - 或改用
std::variant<circle square></circle>+ 访问者模式,或自定义 type-erased 包装器
CRTP 的复杂点不在语法,而在它把所有约束都压到编译期:类型必须显式、签名必须字面一致、继承必须干净、定义必须可见——任何一个环节松动,整个链条就断,而且断得无声无息,只留一行 static_cast 报错在那等你猜。


















