C++ Academy · 课时

CRTP 惯用法

奇异递归模板模式

第 1 / 4 课13 个步骤

CRTP 惯用法 是 CoddyKit 上的免费 C++ Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C++ Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C++ Academy 课程共包含 4 节课。

什么是 CRTP

奇特递归模板模式(CRTP)是一种 C++ 惯用法:类 Derived 继承自使用 Derived 自身实例化的模板基类。

  • 形式:class Derived : public Base<Derived>
  • 基类在编译期获知其派生类型
template <typename T>
class Base {};

class Derived : public Base<Derived> {};

核心机制

在基类内部,您可以将 this 指针 static_cast 为派生类型。由于派生类型是模板参数,这是完全发生在编译期的转换,不会产生运行时开销。

#include <iostream>

template <typename T>
class Base {
public:
    void interface() {
        static_cast<T*>(this)->implementation();
    }
};

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

int main() {
    Derived d;
    d.interface();
    return 0;
}

为什么叫“奇特递归”

这个名称源于一个表面上的悖论:基类会引用派生类,而编写基类模板时,派生类尚未完全定义。

  • 它之所以可行,是因为模板只有在使用时才会实例化
  • 实例化时,Derived 已经是完整类型

第一个实际用途

CRTP 让基类能够提供可复用的功能,并回调派生类。这里,基类提供了一个 print(),将工作委托给派生类的 name()。

#include <iostream>
#include <string>

template <typename T>
struct Named {
    void print() {
        std::cout << static_cast<T*>(this)->name() << "\n";
    }
};

struct Cat : Named<Cat> {
    std::string name() { return "Cat"; }
};

int main() {
    Cat c;
    c.print();
    return 0;
}

没有虚函数表

与运行时多态不同,CRTP 不需要 virtual 关键字,也不需要在每个对象中保存虚函数表指针。分派完全在编译期完成。

  • 对象更小(没有隐藏的虚函数表指针)
  • 调用可以被内联

统计实例数量

CRTP 的一个经典应用是对象计数器。每个派生类都会自动获得自己的静态计数器。

#include <iostream>

template <typename T>
struct Counter {
    static inline int count = 0;
    Counter() { ++count; }
    ~Counter() { --count; }
};

struct Widget : Counter<Widget> {};

int main() {
    Widget a, b;
    std::cout << Widget::count << "\n";
    return 0;
}

每个 Derived 都有自己的状态

由于 Counter<Widget> 和 Counter<Gadget> 是不同的模板实例化,每个实例都有独立的静态 count。使用单个非模板基类无法实现这种隔离。

#include <iostream>

template <typename T>
struct Counter {
    static inline int count = 0;
    Counter() { ++count; }
};

struct A : Counter<A> {};
struct B : Counter<B> {};

int main() {
    A a1, a2;
    B b1;
    std::cout << A::count << " " << B::count << "\n";
    return 0;
}

这里的 static_cast 是安全的

只有当 Base<T> 类型的每个对象确实都是 T 时,基类内部的向下转换才是安全的。CRTP 从结构上保证了这一点,因为 T 就是继承该基类的类。

  • 绝不要为不相关的类实例化 Base<Wrong>

通过 CRTP 实现比较运算符

通过继承比较辅助类,您可以从单个 operator< 生成完整的一组比较运算符。

#include <iostream>

template <typename T>
struct Comparable {
    friend bool operator>(const T& a, const T& b) { return b < a; }
    friend bool operator==(const T& a, const T& b) { return !(a < b) && !(b < a); }
};

struct Num : Comparable<Num> {
    int v;
    Num(int x) : v(x) {}
    friend bool operator<(const Num& a, const Num& b) { return a.v < b.v; }
};

int main() {
    std::cout << (Num(3) > Num(2)) << "\n";
    return 0;
}

受保护的构造函数防护

为了防止基类被意外误用,可以将其构造函数设为 protected,这样只有派生类才能创建它。

#include <iostream>

template <typename T>
class Base {
protected:
    Base() = default;
public:
    void run() { static_cast<T*>(this)->step(); }
};

class Job : public Base<Job> {
public:
    void step() { std::cout << "step\n"; }
};

int main() {
    Job j;
    j.run();
    return 0;
}

标准库中的 CRTP

CRTP 广泛存在于真实的 C++ 库中。

  • std::enable_shared_from_this<T> 使用了它
  • 许多表达式模板数学库(Eigen)依赖它
  • 范围和迭代器辅助工具也使用类似模式

识别出 X : Base<X> 这种形式,有助于您阅读高级代码。

快速检查

测试您对 CRTP 形式的理解。

回顾

您学习了 CRTP 惯用法。

  • Derived : Base<Derived> 将具体类型传递给基类
  • static_cast<T*>(this) 可以实现编译期分派
  • 没有虚函数表,对象更小,调用可以内联
  • 用于计数器、比较混入类和库辅助工具
免费开始

用 AI 导师学习 C++ — 免费

在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。

课程
51
课程
203

常见问题解答

「CRTP 惯用法」课时是免费的吗?

是的 — 「CRTP 惯用法」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C++ Academy 课程的其余内容,请升级到 CoddyKit PRO。 C++ Academy 课程共包含 4 节课。

「CRTP 惯用法」这节课中我会学到什么?

奇异递归模板模式 你通过在浏览器中直接运行的动手代码来练习 C++ Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 C++ Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 C++ Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。

「CRTP 惯用法」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 C++ Academy 课中编写并运行代码吗?

能。每节 C++ Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. CRTP 惯用法
  2. 静态多态
  3. 使用 CRTP 实现混入
  4. 何时使用 CRTP
← 返回 C++ Academy