CRTP通过基类模板参数传入派生类类型实现编译期多态,零运行时开销;需显式指定派生类名、static_cast强转、成员前置声明,并用static_assert约束接口。

CRTP 的基本写法:让基类接受派生类作为模板参数
CRTP(Curiously Recurring Template Pattern)本质是基类把派生类类型作为模板参数传入,从而在编译期获得派生类的完整定义。它不是运行时多态,不依赖 vtable,因此零开销。
典型结构是:Base<derived></derived> 继承自 Derived,而 Derived 又继承自 Base<derived></derived>:
template <typename Derived>
struct Base {
void interface() {
static_cast<Derived*>(this)->implementation(); // 编译期绑定
}
};
<p>struct MyDerived : Base<MyDerived> {
void implementation() { /<em> 具体逻辑 </em>/ }
};
</p>- 必须用
static_cast<derived>(this)</derived>强转,不能用dynamic_cast(无 RTTI,也不支持) - 派生类名必须在基类模板实参中显式写出,不能用
auto或推导 - 基类里调用派生类成员前,该成员必须已声明(注意定义顺序,避免“未声明就使用”错误)
为什么不用 virtual 函数?CRTP 如何规避虚函数开销
虚函数调用需查 vtable + 间接跳转,现代 CPU 难以预测分支,尤其在 tight loop 中明显拖慢。CRTP 把调用完全内联化——只要 implementation() 是 inline 友好(非递归、无复杂控制流),编译器通常能整个展开。
对比来看:
立即学习“C++免费学习笔记(深入)”;
- 虚函数版本:每次调用产生至少 1 次指针解引用 + 1 次跳转
- CRTP 版本:若
interface()被内联,implementation()直接展开进调用点,无间接成本 - 但 CRTP 失去运行时灵活性:无法把不同
Base<t></t>类型存进同一容器(除非用类型擦除补救)
常见误用:静态断言缺失导致编译失败难定位
CRTP 依赖派生类提供特定接口,但编译器不会主动检查。一旦 Derived 忘了定义 implementation(),错误信息往往指向 static_cast<derived>(this)->implementation()</derived> 这一行,报 “no member named ‘implementation’”,非常模糊。
加一层编译期约束能提前暴露问题:
template <typename Derived>
struct Base {
private:
template <typename T>
static auto check_implementation(int) -> decltype(std::declval<T>().implementation(), std::true_type{});
template <typename>
static std::false_type check_implementation(...);
<pre class="brush:php;toolbar:false;">static_assert(decltype(check_implementation<Derived>(0))::value,
"Derived must define 'void implementation()'");public:
void interface() {
static_cast
- 这个
static_assert在实例化Base<derived></derived>时立即触发,错误位置更靠近用户代码 - C++17 可改用
constexpr if+is_detected_v(需自定义 trait),更简洁 - 别忘了头文件包含
<type_traits></type_traits>和<utility></utility>
实际场景:何时该选 CRTP 而不是普通继承
CRTP 不是通用替代品,只在明确需要「零成本抽象」且类型关系固定时才值得引入。
- 数学库中的表达式模板(如
Vector<double> + Vector<double></double></double>延迟求值) - 可组合的策略类(
Logger<filesink jsonformat></filesink>),每个策略通过 CRTP 注入到基骨架 - 需要统一接口但拒绝虚函数开销的硬件抽象层(HAL)驱动封装
- 反例:需要运行时切换行为的 GUI 控件(按钮/滑块共用事件分发),CRTP 会迫使你写大量重复模板特化
真正容易被忽略的是:CRTP 模板实例化会显著增加编译时间与目标码体积——每个 Base<t></t> 都是一份独立代码副本。如果派生类太多,得权衡二进制膨胀是否可接受。










