crtp是唯一零开销编译期多态方案,要求基类为模板、派生类显式继承并传入自身完整类型,static_cast(this)调用需公有单继承且函数签名字面一致,否则立即编译失败。

CRTP 不是“不用虚函数就能跑通”的技巧,它是编译期强契约:漏一个模板实参、少一个 const、传错派生类类型名,编译器立刻报错——但错误往往指向 static_cast<derived>(this)->func()</derived> 那一行,而真正问题藏在派生类定义里。
为什么 class Derived : public Base 一定编译失败
因为 Base 是模板名,不是类型。编译器需要具体特化才能生成代码,比如 Base<derived></derived>。漏掉模板参数等于没给钥匙,门都打不开。
-
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()都失败 - 若派生类本身是模板(如
template<typename t> class Vec3</typename>),必须传入完整特化名:class Vec3f : public Base<vec3>></vec3>,不能只写Vec3 - 前置声明
class Derived;后就继承Base<derived></derived>?会触发incomplete type错误——基类实例化时需读取Derived的完整定义(含成员签名)
static_cast<derived>(this)</derived> 调用失败的真正原因
这个转换本身不崩溃,但它合法的前提是标准公有单继承布局。一旦破坏,static_cast 就变成未定义行为——Clang/GCC 开 -Wall 通常会警告,但不拦着你编译通过。
- 私有或保护继承 → 编译失败:
conversion from 'Base*' to 'Derived*' is inaccessible - 多重继承(如
class D : public Base<d>, public Other</d>)→this指针偏移错位,->func()可能读到其他基类内存 - 虚继承 → 内存布局不可预测,
static_cast偏移失效 - 在基类构造函数里调
static_cast<derived>(this)->func()</derived>→ 危险!此时Derived成员尚未构造,哪怕函数体只读this地址也属未定义
函数签名必须字面一致,否则编译器不认账
CRTP 没有名称查找容错、不支持重载解析、不自动适配 const 或返回类型协变。调用链完全静态,差一个字符就断。
- 基类写
static_cast<derived>(this)->draw_impl()</derived>,派生类就必须提供公有、非模板、签名完全一致的void draw_impl() - 拼错名:
drawImplvsdraw_impl→ 编译错误指向static_cast行,实际缺定义 -
const不匹配:基类调非const版本,派生类只实现void draw_impl() const→ 报no matching member function - 参数类型必须精确:基类调
process(int),派生类写process(long)或process(int, int = 0)都不行 - 建议在基类关键调用点加
static_assert校验,例如:static_assert(std::is_same_v<decltype>().getValue()), int>, "Derived::getValue() must return int");</decltype>
为什么 CRTP 对象不能放进 std::vector<base>>
Base<circle></circle> 和 Base<square></square> 是两个完全无关的类型,没有公共基类,也不满足隐式转换关系。C++ 没有“模板通配符”语法。
-
std::vector<base>>语法非法 —— C++ 不支持模板通配符 -
std::vector<:unique_ptr>>></:unique_ptr>无法容纳Base<square></square>,类型不兼容 - 若真需运行时混合持有,只能外层再套一层虚基类(如
Drawable { virtual void draw() = 0; }),让 CRTP 派生类同时继承它;或者用std::variant<base>*, Base<square>*> </square>手动管理 - 每个
Base<derived></derived>都会独立实例化全部成员函数,大量相似派生类易导致二进制膨胀——这不是 bug,是契约代价
最常被忽略的一点:CRTP 的错误发生在模板实例化期,但默认报错位置极具误导性。真正该检查的,永远是派生类是否完整定义、是否严格匹配签名、是否以 public 单继承方式参与契约——而不是盯着基类那行 static_cast 发呆。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











