crtp是c++中唯一零虚函数、零尺寸、零间接调用开销的编译期多态方案,依赖模板强契约、static_cast(this)安全转型、精确函数签名匹配及if constexpr编译期分支,但天然排斥类型擦除。

CRTP 是目前 C++ 中唯一能同时做到零虚函数、零对象尺寸开销、零间接调用成本的编译期多态方案。它不是语法糖,而是一套编译期强契约:写错一个模板实参,编译器立刻报错;漏实现一个函数,错误常指向 static_cast 行,但根源在派生类缺失定义。
为什么 class Derived : public Base 一定编译失败
因为 Base 是模板名,不是类型。编译器看到 Base 就会报 error: use of class template 'Base' requires template arguments。
-
class Circle : public Base<void></void>同样失败:虽然能实例化Base<void></void>,但它和Circle之间没有继承关系,static_cast<circle>(this)</circle>无法通过类型检查 -
class Circle : public Base<circle></circle>才是唯一合法写法——模板参数必须闭环,且Circle必须是完整定义(不能是前向声明) - 若派生类自身是模板(如
template<typename t> class Vec3</typename>),则继承时必须传入特化后的完整类型:class Vec3f : public Base<vec3>></vec3>,不可简写为Vec3
static_cast<derived>(this)</derived> 是唯一合法转型方式
CRTP 的安全性全部依赖这个转换。它要求编译器在模板实例化时就确认 Base<derived></derived> 和 Derived 存在公有、标准布局的继承关系。
-
dynamic_cast直接被禁用:它要求基类带虚函数,违反“不带虚函数”前提,报错cannot dynamic_cast because 'Base' is not polymorphic -
reinterpret_cast是危险操作:绕过类型系统,一旦Derived含多重继承或空基类优化,this地址偏移就可能错位,访问成员即未定义行为(UB) - 只有
static_cast<derived>(this)</derived>能被 Clang/GCC 在-Wall下校验继承路径;若误用于非派生类,通常在构造函数里就报invalid static_cast - 注意:基类构造函数体内可执行该转换,但此时派生类成员尚未构造,禁止调用派生类未完成初始化的成员函数
派生类函数签名必须和基类调用点字面一致
CRTP 不做运行时名称查找、重载解析或 const 适配。基类中 static_cast<derived>(this)->draw_impl()</derived> 这一行,强制要求 Derived 精确提供同名、同参数、同 cv 限定、同返回类型的公有成员函数。
-
void draw_impl() const和void draw_impl()视为两个完全不同的函数 - 基类调用非 const 版本,派生类只提供 const 版本 → 编译失败,错误指向
static_cast行 - 参数类型差一个
const、引用类别(&vs&&)、默认参数是否声明,都会导致匹配失败 - 建议在基类关键调用点加
static_assert主动校验,例如:static_assert(std::is_same_v<decltype>().get_value()), int>, "Derived must implement get_value() returning int")</decltype>
if constexpr 实现编译期分支但所有分支必须可实例化
当不同派生类需在统一接口中执行差异化逻辑时,不能用 typeid 或虚函数分发,而应靠 if constexpr 在模板函数内裁剪。
- 分支判断必须基于编译期常量表达式,如
std::is_same_v<derived circle></derived> - 每个分支内调用的函数必须已在对应派生类中明确定义;即使该分支未被选中,编译器仍会尝试解析其内容
- 不能在未选中的分支里调用仅存在于其他派生类的函数,否则触发硬错误
- 重度行为分化建议拆成独立模板参数或策略类,而非堆砌
if constexpr
Base<circle>*</circle> 和 Base<square>*</square> 放进同一个 std::vector,因为它们是完全无关的类型,没有公共基类,也不支持隐式转换。想统一持有,只能退回到动态多态,或用 std::variant + 访问者,或自定义 type-erased 包装器——这些选择本身就意味着放弃 CRTP 的零开销承诺。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











