自学内容网 自学内容网

C++之奇异递归模板CRTP(Curiously Recurring Template Pattern)

1.奇异递归模板CRTP

1.1 介绍

CRTP(Curiously Recurring Template Pattern)是一种常用的设计模式,通过将派生类作为模板参数传递给基类,允许基类使用派生类的特性。以下是 CRTP 的基本实现:

template<typename T>
struct Base {
    T value;
    Base(T v) : value(v) {}
    void print() {
        std::cout << value << std::endl;
    }
};

template<typename T>
struct Derived : public Base<T> {
    using Base<T>::Base;
    void print() {
        std::cout << "Derived: " << this->value << std::endl;
    }
};

在这个示例中,Base 类接受一个类型 T,并且 Derived 类通过 CRTP 继承了 Base,可以重写 print 方法,展示了如何实现静态多态。

1.2 使用场景

  • 代码复用:由于子类派生基类,可以复用基类的方法
  • 编译时多态:基类是一个模板类,能够获得传递进来的派生类,然后可以调用派生类的方法,达到多态的效果,与运行时多态相比没有虚表开销。

1.3 代码复用

1.3.1 访问者模式

访问者模式是一种对象行为模式,它允许在不修改类的情况下向类添加新的操作。以下是访问者模式的实现:

class TextFile;
class VideoFile;

struct Visitor {
    virtual void visit(VideoFile&) = 0;
    virtual void visit(TextFile&) = 0;
    virtual ~Visitor() = default;
};

struct Elem {
    virtual void accept(Visitor& v) = 0;
    virtual ~Elem() = default;
};

struct VideoFile : Elem {
    virtual void accept(Visitor& v) override {
        v.visit(*this);
    }
};

struct TextFile : Elem {
    virtual void accept(Visitor& v) override {
        v.visit(*this);
    }
};

struct ConcreteVisitor : Visitor {
    void visit(VideoFile& video) override {
        std::cout << "Visiting Video File" << std::endl;
    }

    void visit(TextFile& text) override {
        std::cout << "Visiting Text File" << std::endl;
    }
};

通过 Visitor 接口,我们可以轻松地为不同的文件类型实现访问逻辑,而无需更改文件类的代码。

1.3.2 奇异递归模板实现访问者模式

使用奇异递归模板(CRTP)实现代码复用是一个有效的方法,可以减少重复代码并增强类型安全性。以下是一个改进后的示例,展示如何通过 CRTP 来实现 AutoDispatchedElem 类,结合访问者模式进行代码复用。

#include <iostream>

// 前向声明
class VideoFile2;
class TextFile2;

// 访问者接口
struct Visitor2 {
    virtual void visit(VideoFile2&) = 0;
    virtual void visit(TextFile2&) = 0;
    virtual ~Visitor2() = default;
};

// 基类
struct Elem2 {
    virtual void accept(Visitor2& v) = 0;
    virtual ~Elem2() = default;
};

// CRTP 基类
template<typename T>
struct AutoDispatchedElem : public Elem2 {
    void accept(Visitor2& v) override {
        v.visit(static_cast<T&>(*this));
    }
};

// VideoFile2 类
struct VideoFile2 : AutoDispatchedElem<VideoFile2> {
    void print() {
        std::cout << "Video File 2" << std::endl;
    }
};

// TextFile2 类
struct TextFile2 : AutoDispatchedElem<TextFile2> {
    void print() {
        std::cout << "Text File 2" << std::endl;
    }
};

// 具体的 Visitor 实现
struct ConcreteVisitor2 : Visitor2 {
    void visit(VideoFile2& video) override {
        video.print();
    }

    void visit(TextFile2& text) override {
        text.print();
    }
};

// 测试函数
int test2() {
    VideoFile2 video;
    TextFile2 text;

    ConcreteVisitor2 visitor;

    // 访问 VideoFile2 和 TextFile2
    video.accept(visitor);
    text.accept(visitor);

    return 0;
}

int main() {
    test2();
    return 0;
}

代码解析:
CRTP 基类 AutoDispatchedElem:
该类继承自 Elem2,并实现了 accept 方法。通过使用 static_cast,它将 this 转换为特定的类型 T,允许访问者调用特定的 visit 方法。

VideoFile2 和 TextFile2 类:
这两个类分别继承自 AutoDispatchedElem,在其中实现 print 方法。它们的 accept 方法会被自动派发到合适的访问者实现。

ConcreteVisitor2 类:
这个类实现了 Visitor2 接口,具体实现了 visit 方法,用于打印 VideoFile2 和 TextFile2 的信息。

通过 CRTP,AutoDispatchedElem 类能够复用代码,同时保持类型安全。访问者模式和 CRTP 的结合使得代码更为简洁,易于维护。你可以根据需要扩展此模式,添加更多文件类型或操作。

1.4 编译时多态

通过 CRTP,我们可以实现编译时多态,避免虚函数调用的开销。

#include <iostream>

// CRTP 基类
template<typename T>
struct Animal {
    // 调用具体实现的方法
    void bark() {
        static_cast<T&>(*this).barkImpl();
    }
};

// Cat 类
class Cat : public Animal<Cat> {
public:
    void barkImpl() { std::cout << "Meow" << std::endl; }
};

// Dog 类
class Dog : public Animal<Dog> {
public:
    void barkImpl() { std::cout << "Woof" << std::endl; }
};

// 统一的操作函数
template<typename T>
void play(Animal<T>& animal) {
    animal.bark();
}

int main() {
    Cat cat;
    Dog dog;

    // 通过统一接口调用各自的行为
    play(cat); // 输出: Meow
    play(dog); // 输出: Woof

    return 0;
}

代码解析
CRTP 基类 Animal:
Animal 模板类接受一个类型参数 T,并定义了一个公共方法 bark。在这个方法中,使用 static_cast 将 this 转换为 T 类型,然后调用 barkImpl 方法。这个方法在派生类中实现。

派生类 Cat 和 Dog:
Cat 和 Dog 类分别继承自 Animal 和 Animal。它们实现了具体的 barkImpl 方法,分别输出 “Meow” 和 “Woof”。

操作函数 play:
play 函数接受一个 Animal 的引用,调用 bark 方法。这种方式允许我们以统一的接口操作不同类型的动物。

1.5 优势总结

  • 性能优化:由于没有虚函数的开销,程序执行速度更快。
  • 编译时类型检查:类型错误在编译时被捕获,提高了代码的安全性。
  • 易于扩展:只需创建新的派生类并实现 barkImpl 方法,就可以轻松扩展新类型。

通过 CRTP,我们可以在 C++ 中实现高效的编译时多态。此模式使得代码更加简洁、可维护,同时也提高了运行效率。使用 CRTP 的模式在许多实际应用中都非常有用,尤其是在需要高性能的场景中。


原文地址:https://blog.csdn.net/qq_45254369/article/details/142534782

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!