• 初探C++ CRTP(奇异的递归模板模式)


    1.什么是CRTP?

    简单来说,CRTP有两大特性:

    • 继承自模板类。
    • 派生类将自身作为参数传给模板类。
    // 我们先定义一个模板类作为基类
    template <typename T>
    class Base
    {
        ...
    };
     // 定义一个派生类,这个类继承以自身作为参数的基类
    class Derived : public Base<Derived>
    {
        ...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    问题来了,为什么要这样做呢?

    • 这样做的目的其实很明确,从基类对象的角度来看,派生类对象其实就是本身,这样的话只需要用一个static_cast就可以把基类转化成派生类,从而实现基类对象对派生对象的访问。
    • eg:
    template <typename T>
    class Base
    {
    public:
        void doSomething()
        {
            T& derived = static_cast<T&>(*this);
        }
    };
    
    class Derived : public Base<Derived>
    {
    public:
        void doSomething()
        {
             std::cout << " Derived class " << std::endl;
        }  
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 这里将基类转换成派生类用的是static_cast静态绑定,而普通基类转派生类用的是dynamic_cast动态绑定。
      动态绑定的目的是为了确保你所转化的派生类是正确的,而对于CRTP来说,基类是继承于模板类的参数,也就是派生类本身。 这也正是CRTP这种设计的目的。

    2.CRTP的优点

    多态是个很好的特性,但是动态绑定比较慢,因为要查虚函数表。

    • 而使用 CRTP,完全消除了动态绑定,降低了继承带来的虚函数表查询开销

    3.静态多态(Static polymorphism)

    eg:

    • 以Base::interface() 为例,在Derived : Base中,Base是先于Derived而存在的,所以当Base::interface()被声明时,编译器并不知道Derived的存在的,所以此时 Base::interface() 并不会被实例化。
    • 只有当Base::interface()被调用时,才会被实例化,而此时编译器也已经知道了 Derived::implementation()的声明了。
    • 这里等同于通过查询虚函数动态绑定以达到多态的效果,但省略了动态绑定虚函数查询的时间。
    template <class T> 
    struct Base
    {
        void interface()
        {
            // ...
            static_cast<T*>(this)->implementation();
            // ...
        }
    
        static void static_func()
        {
            // ...
            T::static_sub_func();
            // ...
        }
    };
    
    struct Derived : Base<Derived>
    {
        void implementation()static void static_sub_func();
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2.轻松地实现各个子类实例创建和析构独立的计数

    eg:

    • 每次X或者Y实例化时,counter或者 counter就会被调用,对应的就会增加对创建对象的计数。
    • 同样,每次X或者Y析构后,对应的减少objects_alive。这里最重要的是实现了对不同子类单独的计数。
    template <typename T>
    struct counter
    {
        static int objects_created;
        static int objects_alive;
    
        counter()
        {
            ++objects_created;
            ++objects_alive;
        }
        
        counter(const counter&)
        {
            ++objects_created;
            ++objects_alive;
        }
    protected:
        ~counter() // objects should never be removed through pointers of this type
        {
            --objects_alive;
        }
    };
    template <typename T> int counter<T>::objects_created( 0 );
    template <typename T> int counter<T>::objects_alive( 0 );
    
    class X : counter<X>
    {
        // ...
    };
    
    class Y : counter<Y>
    {
        // ...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    3.多态链(Polymorphic chaining)

    eg:

    • printer定义了打印的方法,CoutPrinter是Printer的子类,并且添加了一个设置打印颜色的方法。
    class Printer
    {
    public:
        Printer(ostream& pstream) : m_stream(pstream) {}
     
        template <typename T>
        Printer& print(T&& t) { m_stream << t; return *this; }
     
        template <typename T>
        Printer& println(T&& t) { m_stream << t << endl; return *this; }
    private:
        ostream& m_stream;
    };
    
    class CoutPrinter : public Printer
    {
    public:
        CoutPrinter() : Printer(cout) {}
    
        CoutPrinter& SetConsoleColor(Color c)
        {
            // ...
            return *this;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 接下来我们看看下面这行代码
      前半段CoutPrinter().print("Hello ")调用的是Printer实例,后面接着SetConsoleColor(Color.red)实际上又需要调用CoutPrinter实例,这样编译器就会报错。
    CoutPrinter().print("Hello ").SetConsoleColor(Color.red).println("Printer!");
    
    • 1
    • 而CRTP就可以很好的解决这个问题,代码如下:
    // Base class
    template <typename ConcretePrinter>
    class Printer
    {
    public:
        Printer(ostream& pstream) : m_stream(pstream) {}
     
        template <typename T>
        ConcretePrinter& print(T&& t)
        {
            m_stream << t;
            return static_cast<ConcretePrinter&>(*this);
        }
     
        template <typename T>
        ConcretePrinter& println(T&& t)
        {
            m_stream << t << endl;
            return static_cast<ConcretePrinter&>(*this);
        }
    private:
        ostream& m_stream;
    };
     
    // Derived class
    class CoutPrinter : public Printer<CoutPrinter>
    {
    public:
        CoutPrinter() : Printer(cout) {}
     
        CoutPrinter& SetConsoleColor(Color c)
        {
            // ...
            return *this;
        }
    };
     
    // usage
    CoutPrinter().print("Hello ").SetConsoleColor(Color.red).println("Printer!");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    4.多态的拷贝构造(Polymorphic copy construction)。

    当我们用到多态时,经常会需要通过基类的指针来复制子对象。

    • 通常我们可以通过在基类里面构造一个clone()虚函数,然后在每个子类里面定义这个clone()函数。使用CRTP可以让我们避免反复地在子类中去定义clone()函数。
    • 看到virtual,就说明这里还是需要运行时的多态,因为实例化后的子类类型无法在编译期进行确定
    • CRTP实现不了动态多态
    // Base class has a pure virtual function for cloning
    class AbstractShape {
    public:
        virtual ~AbstractShape () = default;
        virtual std::unique_ptr<AbstractShape> clone() const = 0;
    };
    
    // This CRTP class implements clone() for Derived
    template <typename Derived>
    class Shape : public AbstractShape {
    public:
        std::unique_ptr<AbstractShape> clone() const override {
            return std::make_unique<Derived>(static_cast<Derived const&>(*this));
        }
    
    protected:
       // We make clear Shape class needs to be inherited
       Shape() = default;
       Shape(const Shape&) = default;
    };
    
    // Every derived class inherits from CRTP class instead of abstract class
    class Square : public Shape<Square>{};
    
    class Circle : public Shape<Circle>{};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
  • 相关阅读:
    论文学习——基于枢轴点预测和多样性策略混合的动态多目标优化
    怎么修改jupyter lab 的工作路径而不是直接再桌面路径打开
    SHC加密sh脚本
    达梦数据库备份策略
    敲黑板|六大问题不搞清楚,一造将无法报名成功
    CV基础常用知识点
    设计模式:观察者模式(C++实现)
    Vue3 - Suspense 组件介绍及使用方法
    石头剪刀布游戏(C语言)
    电力监控系统的组网方案与设计
  • 原文地址:https://blog.csdn.net/u011436427/article/details/125597908