欢迎光临
我们一直在努力

C++ 开发必须了解的 CRTP(奇异递归模板模式)

C++ 开发必须了解的 CRTP(奇异递归模板模式)

  • 一、详解
    • 1、 什么是 CRTP?
    • 2、 CRTP 的核心原理
      • 2.1、 静态多态的实现
      • 2.2 、关键特性
    • 3、CRTP 的典型应用场景
      • 3.1、 静态接口实现
      • 3.2、 对象计数
      • 3.3 、链式调用
    • 4、CRTP 与虚函数的对比
    • 5、 CRTP 的最佳实践
      • 5.1 、使用 `static_cast` 而非 `dynamic_cast`
      • 5.2、 提供保护性析构函数
      • 5.3、 使用 CRTP 辅助宏(可选)
    • 6、 CRTP 的局限性
    • 7、 实际项目中的 CRTP 示例
      • 7.1 、访问者模式实现
      • 7.2、 序列化框架
    • 8、总结
  • 二、代码示例
    • 1、示例代码
    • 2、运行结果

在这里插入图片描述

一、详解

1、 什么是 CRTP?

CRTP(Curiously Recurring Template Pattern,奇异递归模板模式)是 C++ 模板元编程中的一种高级设计模式。它的核心思想是:一个类模板的派生类,将自己作为基类模板的类型参数。

这种模式的名字来源于其奇特的语法形式:

template <typename Derived>
class Base {
// 基类实现
};

class Derived : public Base<Derived> { // 注意这里:Derived 作为模板参数
// 派生类实现
};

从语法上看,Derived 继承自 Base<Derived>,这形成了一个"递归"的继承关系。但实际上,这里的"递归"是编译时的,不会导致无限循环。

2、 CRTP 的核心原理

2.1、 静态多态的实现

CRTP 的核心优势在于实现静态多态(编译时多态),这与传统的虚函数动态多态形成对比:

// 传统虚函数方式(动态多态)
class Animal {
public:
virtual void speak() = 0;
};

class Dog : public Animal {
public:
void speak() override { cout << "Woof!" << endl; }
};

// CRTP 方式(静态多态)
template <typename Derived>
class AnimalBase {
public:
void speak() {
static_cast<Derived*>(this)->speakImpl();
}
};

class Dog : public AnimalBase<Dog> {
public:
void speakImpl() { cout << "Woof!" << endl; }
};

2.2 、关键特性

  • 零运行时开销:所有调用在编译时确定,没有虚函数表(vtable)开销
  • 类型安全:编译器在编译时检查类型正确性
  • 内联优化:编译器可以进行更好的内联优化
  • 3、CRTP 的典型应用场景

    3.1、 静态接口实现

    template <typename Derived>
    class Comparable {
    public:
    bool operator==(const Derived& other) const {
    return !(static_cast<const Derived&>(*this) < other) &&
    !(other < static_cast<const Derived&>(*this));
    }

    bool operator!=(const Derived& other) const {
    return !(*this == other);
    }
    };

    class MyInt : public Comparable<MyInt> {
    public:
    int value;

    MyInt(int v) : value(v) {}

    bool operator<(const MyInt& other) const {
    return value < other.value;
    }
    };

    3.2、 对象计数

    template <typename T>
    class Counter {
    public:
    static int getCount() { return count; }

    protected:
    Counter() { ++count; }
    Counter(const Counter&) { ++count; }
    ~Counter() { count; }

    private:
    static int count;
    };

    template <typename T>
    int Counter<T>::count = 0;

    class MyObject : public Counter<MyObject> {
    // 自动获得计数功能
    };

    3.3 、链式调用

    template <typename Derived>
    class Builder {
    public:
    Derived& setX(int x) {
    // 设置逻辑
    return static_cast<Derived&>(*this);
    }

    Derived& setY(int y) {
    // 设置逻辑
    return static_cast<Derived&>(*this);
    }
    };

    class MyBuilder : public Builder<MyBuilder> {
    public:
    MyBuilder& build() {
    // 构建逻辑
    return *this;
    }
    };

    // 使用:支持链式调用
    MyBuilder().setX(10).setY(20).build();

    4、CRTP 与虚函数的对比

    特性CRTP(静态多态)虚函数(动态多态)
    性能 零运行时开销,可内联 有虚函数表开销
    编译时间 编译时实例化,可能增加编译时间 编译时间较短
    二进制大小 可能增加(模板实例化) 相对较小
    灵活性 编译时确定,不够灵活 运行时多态,更灵活
    类型安全 编译时检查,更安全 运行时可能出错

    5、 CRTP 的最佳实践

    5.1 、使用 static_cast 而非 dynamic_cast

    template <typename Derived>
    class Base {
    public:
    void foo() {
    // 正确:使用 static_cast
    auto& derived = static_cast<Derived&>(*this);
    derived.bar();

    // 错误:不要使用 dynamic_cast
    // auto* derived = dynamic_cast<Derived*>(this);
    }
    };

    5.2、 提供保护性析构函数

    template <typename Derived>
    class Base {
    protected:
    ~Base() = default; // 防止通过基类指针删除
    public:
    // 其他接口
    };

    5.3、 使用 CRTP 辅助宏(可选)

    #define CRTP_BASE(Derived) \\
    Derived& derived() { return static_cast<Derived&>(*this); } \\
    const Derived& derived() const { return static_cast<const Derived&>(*this); }

    template <typename Derived>
    class Base {
    public:
    CRTP_BASE(Derived)

    void interface() {
    derived().implementation();
    }
    };

    6、 CRTP 的局限性

  • 编译错误信息晦涩:模板错误信息通常难以理解
  • 不能处理运行时类型:所有类型必须在编译时确定
  • 代码膨胀:每个不同的派生类都会实例化一个基类模板
  • 循环依赖风险:需要小心设计避免编译时循环依赖
  • 7、 实际项目中的 CRTP 示例

    7.1 、访问者模式实现

    template <typename Derived>
    class Element {
    public:
    template <typename Visitor>
    void accept(Visitor& visitor) {
    visitor.visit(static_cast<Derived&>(*this));
    }
    };

    class ConcreteElementA : public Element<ConcreteElementA> {
    // 具体元素实现
    };

    class ConcreteElementB : public Element<ConcreteElementB> {
    // 具体元素实现
    };

    7.2、 序列化框架

    template <typename Derived>
    class Serializable {
    public:
    std::string toJson() const {
    const auto& derived = static_cast<const Derived&>(*this);
    // 使用 derived 的字段生成 JSON
    return "{}"; // 简化示例
    }

    void fromJson(const std::string& json) {
    auto& derived = static_cast<Derived&>(*this);
    // 从 JSON 解析并设置 derived 的字段
    }
    };

    8、总结

    CRTP 是 C++ 模板元编程中的强大工具,它通过编译时多态提供了零开销的抽象能力。虽然学习曲线较陡,但在性能关键的场景中,CRTP 可以显著提升程序效率。

    适用场景:

    • 需要零运行时开销的多态
    • 编译时确定的类型关系
    • 高性能库和框架开发

    不适用场景:

    • 需要运行时动态类型
    • 简单的继承关系
    • 对编译时间敏感的项目

    二、代码示例

    1、示例代码

    #include <iostream>
    #include <memory>
    #include <string>

    // ==============================================
    // 一、基础CRTP骨架:静态多态分发(替代虚函数,0运行时开销)
    // ==============================================
    template <typename Derived>
    class CRTPBase
    {
    public:
    // 统一对外接口
    void process()
    {
    // 安全向下转型,编译期确定调用派生类方法
    static_cast<Derived*>(this)->impl_process();
    }

    void info() const
    {
    static_cast<const Derived*>(this)->impl_info();
    }

    protected:
    // 保护析构,只允许派生类销毁基类
    ~CRTPBase() = default;
    };

    // 派生类1
    class FileWriter : public CRTPBase<FileWriter>
    {
    public:
    void impl_process()
    {
    std::cout << "CRTP静态分发:文件写入数据\\n";
    }
    void impl_info() const
    {
    std::cout << "类型:文件写入器\\n";
    }
    };

    // 派生类2
    class NetSender : public CRTPBase<NetSender>
    {
    public:
    void impl_process()
    {
    std::cout << "CRTP静态分发:网络发送数据包\\n";
    }
    void impl_info() const
    {
    std::cout << "类型:网络发送器\\n";
    }
    };

    // ==============================================
    // 二、CRTP作为Mixin混入:给派生类批量附加能力
    // 特性1:每个派生类独立静态成员变量(互不干扰)
    // 特性2:自动对象计数
    // ==============================================
    template <typename Derived>
    class ObjectCounter
    {
    public:
    ObjectCounter() { ++s_objCount; }
    ObjectCounter(const ObjectCounter&) { ++s_objCount; }
    ~ObjectCounter() { s_objCount; }

    // 静态方法查看当前实例数
    static size_t GetInstanceCount()
    {
    return s_objCount;
    }

    private:
    // 模板不同实例拥有独立静态变量
    static size_t s_objCount;
    };

    // 静态变量初始化
    template <typename Derived>
    size_t ObjectCounter<Derived>::s_objCount = 0;

    // 两个不同类,计数器完全隔离
    class Button : public ObjectCounter<Button> {};
    class Window : public ObjectCounter<Window> {};

    // ==============================================
    // 三、CRTP 编译期强制派生类必须实现指定接口(静态检查)
    // 不实现则直接编译报错,替代运行时断言
    // ==============================================
    template <typename Derived>
    class ForceInterface
    {
    public:
    void run()
    {
    // 如果Derived没有impl_run,编译直接失败
    static_cast<Derived*>(this)->impl_run();
    }

    protected:
    ~ForceInterface() = default;
    };

    class Task : public ForceInterface<Task>
    {
    public:
    void impl_run()
    {
    std::cout << "强制接口:任务执行成功\\n";
    }
    };

    // ==============================================
    // 四、CRTP 实现流式链式调用
    // ==============================================
    template <typename Derived>
    class Chainable
    {
    public:
    Derived& Self()
    {
    return *static_cast<Derived*>(this);
    }
    };

    class StringBuilder : public Chainable<StringBuilder>
    {
    private:
    std::string m_buf;
    public:
    StringBuilder& Append(const std::string& str)
    {
    m_buf += str;
    return Self();
    }

    void Print() const
    {
    std::cout << "链式拼接结果:" << m_buf << '\\n';
    }
    };

    // ==============================================
    // 五、CRTP 禁止拷贝/移动(模板方式统一禁用)
    // ==============================================
    template <typename Derived>
    class NonCopyable
    {
    protected:
    NonCopyable() = default;
    ~NonCopyable() = default;

    // 删除拷贝构造、赋值、移动
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;
    NonCopyable(NonCopyable&&) = delete;
    NonCopyable& operator=(NonCopyable&&) = delete;
    };

    class Resource : public NonCopyable<Resource>
    {
    public:
    void Use()
    {
    std::cout << "独占资源使用中\\n";
    }
    };

    // ==============================================
    // 六、标准库经典CRTP应用:std::enable_shared_from_this
    // ==============================================
    class Node : public std::enable_shared_from_this<Node>
    {
    public:
    std::shared_ptr<Node> GetSelf()
    {
    return shared_from_this();
    }
    void Hello()
    {
    std::cout << "enable_shared_from_this CRTP节点调用\\n";
    }
    };

    // ==============================================
    // 主函数统一测试所有CRTP能力
    // ==============================================
    int main()
    {
    std::cout << "========== 1. CRTP 静态多态分发 ==========\\n";
    FileWriter fw;
    NetSender ns;
    fw.process();
    fw.info();
    ns.process();
    ns.info();

    std::cout << "\\n========== 2. CRTP Mixin 对象计数(独立静态变量) ==========\\n";
    Button b1, b2;
    Window w1;
    std::cout << "Button实例数:" << Button::GetInstanceCount() << '\\n';
    std::cout << "Window实例数:" << Window::GetInstanceCount() << '\\n';

    std::cout << "\\n========== 3. CRTP 编译期强制接口实现 ==========\\n";
    Task t;
    t.run();

    std::cout << "\\n========== 4. CRTP 链式调用 ==========\\n";
    StringBuilder sb;
    sb.Append("Hello ").Append("CRTP ").Append("Chain").Print();

    std::cout << "\\n========== 5. CRTP 禁止拷贝资源类 ==========\\n";
    Resource res;
    res.Use();
    // Resource res2 = res; // 编译报错,拷贝被删除

    std::cout << "\\n========== 6. 标准库CRTP:enable_shared_from_this ==========\\n";
    auto sp = std::make_shared<Node>();
    auto sp2 = sp->GetSelf();
    sp2->Hello();

    return 0;
    }

    2、运行结果

    ========== 1. CRTP 静态多态分发 ==========
    CRTP静态分发:文件写入数据
    类型:文件写入器
    CRTP静态分发:网络发送数据包
    类型:网络发送器

    ========== 2. CRTP Mixin 对象计数(独立静态变量) ==========
    Button实例数:2
    Window实例数:1

    ========== 3. CRTP 编译期强制接口实现 ==========
    强制接口:任务执行成功

    ========== 4. CRTP 链式调用 ==========
    链式拼接结果:Hello CRTP Chain

    ========== 5. CRTP 禁止拷贝资源类 ==========
    独占资源使用中

    ========== 6. 标准库CRTP:enable_shared_from_this ==========
    enable_shared_from_this CRTP节点调用

    D:\\user\\01417804\\桌面\\新建文件夹\\Project1\\x64\\Debug\\Project1.exe (进程 20716)已退出,代码为 0 (0x0)
    要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
    按任意键关闭此窗口. . .

    在这里插入图片描述

    在这里插入图片描述

    赞(0)
    未经允许不得转载:171主机测评 » C++ 开发必须了解的 CRTP(奇异递归模板模式)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址