CRTP

CRTP = Curiously Recurring Template Pattern。核心形式是:让派生类把自己作为模板参数传给基类

template<typename Derived>
struct Base { /* 使用 static_cast<Derived*>(this) 调用派生接口 */ };

struct Derived : Base<Derived> { /* ... */ };

这是 静态多态(compile-time polymorphism) 的一种实现:基类在编译期就能调用派生类的方法,达成“没有虚函数表、零运行时开销”的多态。

工作原理(Why / How)

基类通过 static_cast<Derived*>(this)static_cast<const Derived*>(this) 把自己转回派生类型,然后直接调用派生实现。因为在最终对象内确实包含派生子对象,所以在合法用法下这是安全的(前提:对象确实是 Derived 的实例)。

典型例子(代码 + 说明)

1) 最简单的 CRTP(基类调用派生实现)

#include <iostream>

template<typename Derived>
struct Base {
    void interface() {
        // 编译期绑定到 Derived::implementation()
        static_cast<Derived*>(this)->implementation();
    }
};

struct Derived : Base<Derived> {
    void implementation() { std::cout << "Derived::implementation\n"; }
};

int main() {
    Derived d;
    d.interface(); // 调用 Derived::implementation,且无虚函数开销
}

说明:Base<Derived> 提供公共接口,具体实现由 Derived 提供 —— 静态分派。

2) Mixin:用 CRTP 给派生“混入”操作符

template<typename Derived>
struct EqualityComparable {
    friend bool operator==(Derived const& a, Derived const& b) {
        return a.equals(b); // 要求 Derived 实现 equals
    }
    friend bool operator!=(Derived const& a, Derived const& b) {
        return !(a == b);
    }
};

struct Point : EqualityComparable<Point> {
    int x, y;
    bool equals(Point const& o) const { return x == o.x && y == o.y; }
};

说明:通过 CRTP 可以把通用操作(operator==/!=)写一次,应用到多个派生类型上(mixin 风格)。

3) 每个派生单独静态计数(每个派生类的静态成员)

template<typename T>
struct Counter {
    static int count;
    Counter() { ++count; }
    Counter(const Counter&) { ++count; }
    ~Counter() { --count; }
    static int live() { return count; }
};
template<typename T> int Counter<T>::count = 0;

struct A : Counter<A> {};
struct B : Counter<B> {};
// A::live() 与 B::live() 各自维护独立计数

说明:因为 Counter<A>Counter<B> 是不同的类型,所以每个派生类都有自己的静态计数器。

用途 / 优点(几点要点)

  • 零开销抽象:没有虚表,调用可内联,性能最优。
  • mixin / policy-based design:把共通功能以模板混入多个类。
  • 静态接口约束:在编译期要求派生实现特定接口(配合 C++20 concepts 更直接)。
  • 类型相关静态数据:像上面的计数器,每个派生独立静态变量。
  • 常见库实践:许多库(比如 Eigen、Boost.Iterator 等)使用 CRTP 实现可重用/高性能基础设施。

限制 / 注意点(重要)

  • 不是运行时多态Base<Derived1>Base<Derived2> 是不同类型,不能把不同派生放入同一个 Base<T> 容器。不能用来替代 virtual 用于异构集合或运行时动态绑定。
  • 不要通过非多态基类指针删除对象:基类通常无虚析构,若用 Base<Derived>* p = new Derived; delete p; 将未定义行为。通常把基类析构函数设为 protected(防止通过基类指针删除)。
  • 对象切片:把 Derived 赋值给 Base<Derived> 的值对象会切片(跟普通继承一样)。
  • 编译时间/代码膨胀:CRTP 生成多个模板实例,可能增加编译时间和二进制体积。
  • 使用错误会导致难懂的模板错误信息:如果派生没有实现被调用的方法,编译器报错通常在基类的调用处。

进阶用法(C++20/概念 & 多重 mixin)

  • 使用 C++20 requires / concepts 对派生接口进行显式约束:
template<typename D>
concept HasImpl = requires(D& d) { d.impl(); };

template<HasImpl Derived>
struct Base {
    void interface() { static_cast<Derived&>(*this).impl(); }
};

这样在编译期会给出更清晰的错误信息(如果 Derived 没实现 impl())。

  • 多重 mixin:
struct Derived : MixinA<Derived>, MixinB<Derived> { /* ... */ };

可以把很多功能模块化地混入到同一个派生类。

小结(快速要点)

  • CRTP 是一种静态多态与 mixin 技术:基类模板以派生类为参数。
  • 优点是零运行时开销与强大的复用能力;缺点是不能替代所有虚函数场景、存在模板膨胀和使用注意。
  • 常见用途:实现高性能库、迭代器/表达式模板(e.g. Eigen)、policy/mixin 类、按类型分离的静态数据等。
  • 建议:当你需要编译期多态、想消除虚函数开销或实现可复用 mixin 时用 CRTP;需要运行时可替换对象或异构容器时用虚函数。