crtp核心是基类模板以派生类自身为参数,如template class base; class d : public base;编译期通过static_cast(this)调用派生类成员,实现零开销静态多态。

CRTP 的核心写法:基类模板参数必须是派生类本身
CRTP 要生效,Base 必须在定义时就知道最终派生类型,所以它得是一个模板,且派生类要显式把自身作为模板实参传进去。常见错误是漏掉模板参数或传错类型,比如写成 class Derived : public Base<derived></derived> 才对,写成 class Derived : public Base(非模板)或 Base<void></void> 都会编译失败,因为基类无法调用派生类的静态成员或函数。
典型结构:
template <typename derived>
class Shape {
public:
double area() const { return static_cast<const derived>(this)->do_area(); }
};
<p>class Circle : public Shape<circle> {
double r<em>;
public:
explicit Circle(double r) : r</em>(r) {}
double do<em>area() const { return 3.14159 * r</em> * r_; }
};
</circle></p></const></typename>
为什么能绕过虚函数表:所有调用都在编译期绑定
因为 Shape<circle>::area()</circle> 内部调用的是 Circle::do_area(),而 Circle 是具体已知类型,编译器直接生成调用指令,不查 vtable。只要派生类实现了约定好的接口(如 do_area()),基类模板就能安全调用——这本质是“契约式静态多态”,不是运行时推导。
注意点:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
-
static_cast<const derived>(this)</const>是关键,不能用dynamic_cast(不支持,也没必要) - 派生类函数必须是
public或protected,且签名需匹配基类预期(否则编译报错,不是运行时崩溃) - 如果派生类没实现
do_area(),错误发生在实例化Shape<circle></circle>时,提示类似“no member named 'do_area' in 'Circle'
CRTP 和普通模板继承的区别:基类能“看见”派生类的完整定义
普通模板(如 template<typename t> class Holder</typename>)只依赖 T 的声明;但 CRTP 中,Base<derived></derived> 在定义时往往需要 Derived 的完整定义(尤其当基类要调用其 static 成员、嵌套类型或 sizeof(Derived) 时)。这就导致头文件依赖变紧,容易循环包含。
常见应对方式:
- 把基类定义放在派生类之前,或拆到单独头文件并确保 include 顺序
- 避免在基类模板体中直接使用
sizeof(Derived)或Derived::some_nested_type,改用decltype或延迟到成员函数体内(此时Derived已完全定义) - 不要试图在基类构造函数里调用
static_cast<derived>(this)->xxx()</derived>—— 此时派生类部分尚未构造,行为未定义
什么时候不该用 CRTP:虚函数反而更合适
CRTP 适合编译期确定类型组合、零成本抽象的场景(如表达式模板、小型策略类、可组合的容器基类)。但它不解决运行时类型未知的问题。例如,你有一组 Shape* 指针存进 vector,想统一调用 area(),这时 CRTP 无能为力——因为你无法写出 Shape> 这种泛型指针类型。
容易被忽略的限制:
- 无法动态切换行为(比如运行时根据配置选不同算法),CRTP 行为在编译期固化
- 每个派生类都会实例化一份基类模板,可能增大代码体积(尤其是基类很大、派生类很多时)
- 调试时堆栈显示的是
Shape<circle>::area</circle>而非直观的Circle::area,对新手不友好
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










