• 奇异递归模板


    1. 背景

    奇异递归模板模式(Curiously Recurring Template Pattern,CRTP),CRTP是C++模板编程时的一种惯用法(idiom):把派生类作为基类的模板参数。更一般地被称作F-bound polymorphism CRTP在C++中主要有两种用途:

    • 静态多态(static polymorphism)
    • 添加方法同时精简代码

    2. 奇异递归模板介绍

    2.1 标准范式

    template
    class Base
    {
        // methods within Base can use template to access members of Derived
    };
    class Derived : public Base
    {
        // ...
    };
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这样做的目的是在基类中使用派生类,从基类的角度来看,派生类其实也是基类,通过向下转换[downcast],因此,基类可以通过static_cast把其转换到派生类,从而使用派生类的成员,形式如下:

    template 
    class Base
    {
    public:
        void doWhat()
        {
            T& derived = static_cast(*this);
            // use derived...
        }
    };
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.2 CRTP的特点

    • 继承自模板类;
    • 使用派生类作为模板参数特化基类;

    2.3 CRTP的易错点

    2.3.1 子类传递错误

    当两个类继承自同一个CRTP base类时,如下代码所示,会出现错误(Derived2派生的基类模板参数不是Derived2)。

    class Derived1 : public Base
    {
        ...
    };
    class Derived2 : public Base // bug in this line of code  调用的是Derived1的函数
    {
        ...
    };
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    可用的解决方案如下:

    template 
    class Base
    {
    public:
        // ...
    private:// import
        Base(){};
        friend T;
    }; 
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    通过代码可以看出来,基类中添加一个私有构造函数,并且模板参数T是Base的友元。这样做可行是因为,派生类需要调用基类的构造函数(编译器会默认调用的),由于Base的构造函数是私有的(private),除了友元没有其他类可以访问的,而且基类独立的友元是其实例化模板参数。

    2.3.2 派生类覆盖基类同名成员函数

    3. CRTP 应用

    3.1 静态多态

    template  
    struct Base
    {
        void interface()
        {
            // ...
            static_cast(this)->implementation();
            // ...
        }
    
        static void static_func()
        {
            // ...
            T::static_sub_func();
            // ...
        }
    };
    
    struct Derived : Base
    {
        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

    考虑一个基类,没有虚函数,则它的成员函数能够调用的其它成员函数,只能是属于该基类自身。当从这个基类派生其它类时,派生类继承了所有未被覆盖(overridden)的基类的数据成员与成员函数。如果派生类调用了一个被继承的基类的函数,而该函数又调用了其它成员函数,这些成员函数不可能是派生类中的派生或者覆盖的成员函数。也就是说,基类中是看不到派生类的。但是,基类如果使用了CRTP,则在编译时派生类的覆盖的函数可被选中调用。这效果相当于编译时模拟了虚函数调用但避免了虚函数的尺寸与调用开销(VTBL结构与方法查找、多继承机制)等代价。但CRTP的缺点是不能在运行时做出动态绑定。 不通过虚函数机制,基类访问派生类的私有或保护成员,需要把基类声明为派生类的友元(friend)。如果一个类有多个基类都出现这种需求,声明多个基类都是友元会很麻烦。一种解决技巧是在派生类之上再派生一个accessor类,显然accessor类有权访问派生类的保护函数;如果基类有权访问accessor类,就可以间接调用派生类的保护成员了。这种方法被boost的多个库使用,如:Boost.Python中的def_visitor_access和Boost.Iterator的iterator_core_access。原理示例代码如下:

    template class Base
    {
      private:
        struct accessor : public DerivedT  
        {                                      // accessor类没有数据成员,只有一些静态成员函数
            static int foo(DerivedT& derived)
            {
                int (DerivedT::*fn)() = &DeriveT::do_foo; //获取DerivedT::do_foo的成员函数指针  
                return (derived.*fn)();        // 通过成员函数指针的函数调用
            }
        };                                     // accessor类仅是Base类的成员类型,而没有实例化为Base类的数据成员。
      public:
        DerivedT& derived()                    // 该成员函数返回派生类的实例的引用
        {
           return static_cast(*this);
        }
        int foo()
       {                                       //  该函数具体实现了业务功能
            return accessor::foo( this->derived());
        }
    };
    
    struct Derived : Base   {          //  派生类不需要任何特别的友元声明
      protected:
        int do_foo() 
        {
             // ... 具体实现 
             return 1; 
         }
    };
    • 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

    3.2 多态复制构造

    当使用多态时,常需要基于基类指针创建对象的一份拷贝。常见办法是增加clone虚函数在每一个派生类中。使用CRTP,可以避免在派生类中增加这样的虚函数。

    // Base class has a pure virtual function for cloning
    class Shape {
    public:
        virtual ~Shape() {}
        virtual Shape *clone() const = 0;
    };
    // This CRTP class implements clone() for Derived
    template 
    class Shape_CRTP : public Shape {
    public:
        virtual Shape *clone() const {
            return new Derived(static_cast(*this));
        }
    };
    
    // Nice macro which ensures correct CRTP usage
    #define Derive_Shape_CRTP(Type) class Type: public Shape_CRTP
    
    // Every derived class inherits from Shape_CRTP instead of Shape
    Derive_Shape_CRTP(Square) {};
    Derive_Shape_CRTP(Circle) {};
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3.3 单例基类

    见源文件

  • 相关阅读:
    Windows编译yolov5_obb的nms_rotated模块报错解决
    Android项目---拼图小游戏(下)
    板刷codeforces 1000分
    十、空闲任务及其钩子函数
    【2023,学点儿新Java-46】条件运算符:语法格式及示例;基础练习:获取两个数/三个数中的较大值;星期运算 | 附:测试代码 位运算符的使用 | 运算符优先级
    【SLAM中的问题相关解决方案】
    聊聊Go语言的向前兼容性和toolchain规则
    Pytorch中安装 torch_geometric 详细图文操作(全)
    cartographer 学习
    小程序的性能优化
  • 原文地址:https://blog.csdn.net/u013300049/article/details/126786615