CRTP(Curious Recurring Template Pattern)奇异递归模板模式的理解和应用

CRTP(Curious Recurring Template Pattern)

CRTP是什么?

一种C++模板编程技术

cpp 复制代码
template<typename Derived>
class Base {

}
// 派生类以身试法,打入基类内部
class Derived: public Base<Derived> {

}

之所以称为Curious, 是因为派生类这个还没完全定义好的类型,被用作基类的模板参数。

要解决什么问题?

主要目的是实现编译期多态(静态多态),相应的有运行期多态(动态多态)。二者对比如下:

特性 动态多态 静态多态
机制 虚函数表,运行时查找 模板实例化,编译期确定
开销 间接函数调用,多一次查表,有运行时开销 无需函数开销,函数调用可以被内联
灵活性 高,运行时绑定,非常灵活 低,编译期绑定,类型关系必须确定
典型用途 需要运行时灵活替换行为的场景 需要极致性能,或为相关类提供统一接口/功能

通过以下代码对比以下动态多态和静态多态的区别:

  • 动态多态:

    cpp 复制代码
    class Animal {
    public:
        virtual void speak() const = 0;
    };
    
    class Dog: public Animal {
    public:
        void speak() const override {
            std::cout << "Woof!\n";
        }
    };
    
    class Cat: public Animal {
    public:
        void speak() const override {
            std::cout << "Meow!\n";
        }
    };
    
    void talk(const Animal& a) {
        a.speek();
    }
    
    int main() {
        Dog d;
        Cat c;
        talk(d);
        talk(c);
        return 0;
    }
  • CRTP静态多态

    cpp 复制代码
    template<typename Derived>
    class Animal{
    public:
        void speak() const {
            static_cast<const Derived*>(this)->speakImpl();
        }
    };
    class Dog: public Animal<Dog> {
    public:
        void speakImpl() const {
            std::cout << "Woof!\n";
        }
    };
    class Cat: public Animal<Cat> {
    public:
        void speakImpl() const {
            std::cout << "Meow!\n";
        }
    };
    
    template<typename T>
    void talk(const Animal<T>& a) {
        a.speak();
    }
    
    int main() {
        Dog d;
        Cat c;
        talk(d);
        talk(c);
        return 0;
    }

典型的应用场景

  1. 静态多态

    cpp 复制代码
    #include <iostream>
    #include <vector>
    
    template <typename T>
    class Shape {
    public:
        void draw() const {
            static_cast<const T*>(this)->drawImpl();
        }
        double area() const {
            return static_cast<const T*>(this)->areaImpl();
        }
        void printInfo() const {
            std::cout << "Shape type:" << T::getName()
                    << "Area:" << area() << std::endl;
        }
    };
    
    class Circle: public Shape<Circle> {
    private:
        double radius_;
    public:
        Circle(double r) : radius_(r) {}
        void drawImpl() const {
            std::cout << "Drawing a circle with radius " << radius_ << std::endl;
        }
        double areaImpl() const {
            return 3.1415 * radius_ * radius_;
        }
        static const char* getName() {
            return "Circle";
        }
    };
    
    class Rectangle: public Shape<Rectangle> {
    private:
        double width_;
        double height_;
    
    public:
        Rectangle(double w, double h) : width_(w), height_(h) {}
        void drawImpl() const {
            std::cout << "Drawing a rectangle with width " << width_
                    << " and height " << height_ << std::endl;
        }
        double areaImpl() const {
            return width_ * height_;
        }
        static const char* getName() {
            return "Rectangle";
        }
    };
    
    template<typename T>
    void processShape(const T& shape) {
        shape.draw();
        shape.printInfo();
    }
    
    int main() {
        Circle circle(5.0);
        Rectangle rect(4.0, 5.0);
        processShape(circle);
        processShape(rect);
        return 0;
    }
  2. 对象计数

    cpp 复制代码
    #include <iostream>
    template<typename T>
    class ObjectCounter {
    protected:
        ObjectCounter() {++count;}
        ~ObjectCounter() {--count;}
    public:
        static int getCount() {return count;}
    private:
        static int count;
    };
    template<typename T>
    int ObjectCounter<T>::count = 0;
    
    class Apple: public ObjectCounter<Apple> {};
    class Banana: public ObjectCounter<Banana> {};
    
    int main() {
        Apple a1, a2;
        Banana b1;
    
        std::cout << "Apple instance: " << Apple::getCount()  << std::endl;  // 2
        std::cout << "Banana instance: " << Banana::getCount() << std::endl; // 1
        {
            Banana b2;
            std::cout << "Banana instance: " << Banana::getCount() << std::endl; // 2
        }
        std::cout << "Banana instance: " << Banana::getCount() << std::endl; // 1
        return 0;
    }
  3. 链式调用(Fluent Interface)

    cpp 复制代码
    #include <iostream>
    #include <string>
    
    template<typename Derived>
    class Builder {
    protected:
        Derived& self() {
            return static_cast<Derived&>(*this);
        }
    
    public:
        Derived& setColor(const std::string& color) {
            self().color = color;
            return self();
        }
    
        Derived& setSize(int size) {
            self().size = size;
            return self();
        }
    
        Derived& setTransparent(bool transparent) {
            self().transparent = transparent;
            return self();
        }
    };
    
    class WindowBuilder : public Builder<WindowBuilder> {
    private:
        std::string color = "white";
        int size = 100;
        bool transparent = false;
        
        // 让基类能够访问私有成员
        friend class Builder<WindowBuilder>;
    
    public:
        WindowBuilder() = default;
        WindowBuilder& setTitle(const std::string& title) {
            this->title = title;
            return *this;
        }
    
        void build() const {
            std::cout << "Building window with: " << std::endl
                    << " title: " << title << std::endl
                    << " color: " << color << std::endl
                    << " size: " << size << std::endl
                    << " transparent: " << transparent << std::endl;
        }
    private:
        std::string title = "Untitled";
    
    };
    
    int main() {
        WindowBuilder()
            .setTitle("My application")
            .setColor("blue")
            .setSize(800)
            .setTransparent(true)
            .build();
        return 0;
    }
  4. 代码复用

    先给出示例代码

    示例1,为任何提供了operator==operator<的类型自动添加其他的比较运算符:

    一般情况下,如果你想为多个类都提供多个比较运算符,每个类都要实现一遍所有的运算符重载逻辑,代码非常冗余。

    使用CRTP可以达到一种"能力注入"的效果,像下面的代码,Value这个类只需要实现operator==operator<,然后通过继承EqualityComparableLessThanComparable就将其他的所有比较运算符都注入到了Value这个类中。

    cpp 复制代码
    #include <iostream>
    #include <concepts>
    
    template <typename Derived>
    class EqualityComparable
    {
    public:
        bool operator!=(const Derived &other) const
        {
            return !(static_cast<const Derived &>(*this) == other);
        }
    };
    
    template <typename Derived>
    class LessThanComparable
    {
    public:
        bool operator>(const Derived &other) const
        {
            return other < static_cast<const Derived&>(*this);
        }
        bool operator<=(const Derived &other) const
        {
            return !(other < static_cast<const Derived&>(*this));
        }
        bool operator>=(const Derived &other) const
        {
            return !(static_cast<const Derived&>(*this) < other);
        }
    };
    // Value类提供了operator==运算符,通过CRTP获得了operator!=运算符。这样对所有想添加
    // operator!=的类,只需要继承EqualityComparable即可,无需每个类重载operator!=
    class Value : public EqualityComparable<Value>,
                public LessThanComparable<Value>
    {
    public:
        Value(int val) : value{val} {}
        bool operator==(const Value &other) const
        {
            return value == other.value;
        }
        bool operator<(const Value &other) const
        {
            return value < other.value;
        }
    
    private:
        int value;
    };
    // Value自动拥有了operator!=
    
    int main() {
    
        Value a{1}, b{2};
        std::cout << std::boolalpha;
        std::cout << "a < b: " << (a < b) << std::endl;
        std::cout << "a > b: " << (a > b) << std::endl;
        std::cout << "a <= b: " << (a <= b) << std::endl;
        std::cout << "a >= b: " << (a >= b) << std::endl;
        std::cout << "a != b: " << (a != b) << std::endl;
    
        return 0;
    }

    示例2,使用CRTP实现一个通用的clone接口,每个类都可以被克隆,而无需在每个类中重复实现clone代码。

    cpp 复制代码
    template<typename Derived>
    class Cloneable {
    public:
        virtual std::unique_ptr<Derived> clone() const {
            return std::make_unique<Derived>(static_cast<const Dervied&>(*this));
        }
        virtual ~Cloneable() = default;
    };
    
    class Shape : public Cloneable<Shape> {
    public:
        virtual void draw() const {
            std::cout << "Drawing a shape\n";
        }
    }
    class Circle : public Cloneable<Circle> {
    public:
        Circle(int r) : radius(r) {}
        void draw() const {
            std::cout << "Drawing a circle, radius=" << radius << std::endl;
        }
    private:
        int radius;
    }
    
    int main() {
        Circle c{5};
        auto c2 = c.clone();
        c2->draw();
    
        Shape s;
        auto s2 = s.clone();
        s2->draw();
    
        return 0;
    }
  5. 单例模式

    先给出示例代码

    cpp 复制代码
    #include <iostream>
    
    template<typename T>
    class Singleton {
    protected:
        Singleton() = default;
        ~Singleton() = default;
        Singleton(const Singleton& other) = delete;
        Singleton& operator=(const Singleton& other) = delete;
        Singleton(Singleton&& other) = delete;
        Singleton& operator=(Singleton&& other) = delete;
    
    public:
        static T& getInstance() {
            static T instance;
            return instance;
        }
    };
    
    class Logger: public Singleton<Logger> {
        // 让基类能构造logger
        friend class Singleton<Logger>;
    private:
        Logger() {
            std::cout << "Logger initialized" << std::endl;
        }
    
    public:
        void log(const std::string& message) {
            std::cout << "Log: " << message << std::endl;
        }
        void error(const std::string& message) {
            std::cout << "Error: " << message << std::endl;
        }   
    };
    
    class ConfigManager: public Singleton<ConfigManager>{
        friend class Singleton<ConfigManager>;
    private:
        ConfigManager() {
            std::cout << "ConfigManager initialized" << std::endl;
        }
    
        std::string configValue = "default";
    
    public:
        const std::string& getConfig() const {
            return configValue;
        }
        void setConfig(const std::string& value) {
            configValue = value;
        }
    };
    
    int main() {
        Logger::getInstance().log("This is a log message");
        std::cout << "Config: " << ConfigManager::getInstance().getConfig() << std::endl;
        Logger& l1 = Logger::getInstance();
        Logger& l2 = Logger::getInstance();
        std::cout << "same logger instance? " << (&l1 == &l2) << std::endl;
        return 0;
    }

    单例模式的核心在于控制实例化的过程。

    • Singleton的构造函数为protected,因为CRTP基类需要允许派生类继承但要阻止外部直接实例化;
    • 派生类的构造函数为private,阻止派生类直接实例化;
    • 基类需要调用派生类的构造函数,但派生类的构造函数是private,所以必须声明friend允许基类访问。
      可见,使用CRTP进行单例控制的原理的精妙之处在于权限的组合控制:

    使用CRTP实现单例模式的好处:

    • 代码量骤减:它把一些"体力活"全部封装到了一个模板基类中,比如声明静态方法,私有化构造函数,禁用拷贝构造函数、赋值运算、移动构造构造函数等,这些操作都在基类中做了,不需要具体类重复做这些事了。
    • 强制规范:基类直接删除了拷贝构造函数,意味着任何继承了Singleton的类,自动获得了不可拷贝的特性,这比口头叮嘱程序员"不要拷贝这个类"要有效的多
    • 类型安全:由于使用了CRTPgetInstance()返回的直接就是一个具体的类型,不是一个模糊的void*Base*。不需要在进行类型转化。
相关推荐
Byte不洛2 小时前
基于 C++ 手写 HTTP 服务器:从请求解析到响应构建全流程解析
linux·网络·c++·计算机网络·http
旖-旎2 小时前
前缀和(和为K的子数组)(5)
c++·算法·leetcode·前缀和·哈希算法·散列表
不染尘.2 小时前
背包问题BP
开发语言·c++·算法
2401_874732532 小时前
基于C++的爬虫框架
开发语言·c++·算法
lcj25112 小时前
蓝桥杯C++:数据结构
数据结构·c++·算法
2401_873204652 小时前
C++代码重构实战
开发语言·c++·算法
xiaolang_8616_wjl2 小时前
c++游戏_寻宝猎人_开源
开发语言·c++
暮冬-  Gentle°2 小时前
C++中的策略模式应用
开发语言·c++·算法
Yungoal2 小时前
1:const+volatile解决内存可见性问题,2:共享数据的访问导致竞争条件(Race Condition)
开发语言·c++·架构