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


    简介

    1. 什么叫静态多态和动态多态
      静态多态也称为编译器多态,编译器在编译期根据函数的实参类型推断出要调用那个函数。例如:函数重载和函数模板
      动态多态也就是运行时多态,是指在程序执行期间判断所引用的对象的实例类型,然后根据实际类型调用相应的方法。例如:通过基类类型的引用或者指针调用虚函数
    2. 什么是CRTP
      是Curiously Recurring Template Pattern的缩写,中文翻译可以写成奇异递归模板,是一种把子类类型作为模板参数传给基类的一种模板技巧。

    CRTP可以使得类具有类似virtual function的效果,同时还没有virtual function的调用开销,因为virtual function调用需要通过vptr来找到vtbl进而找到真正的函数指针进行调用,同时对象size相比使用virtual function也会减小,但是CRTP也有明显的缺点,最直接的就是代码可读性降低(模板代码的通病),还有模板实例化之后的code size有可能更大

    CRTP实例

    静态多态用法

    普通多态

    class Base {
    public:
    virtual void foo() = 0;
    virtual void bar() = 0;
    };
    
    class Derived1 : public Base {
    public:
    virtual void foo() override final { cout << "Derived1 foo" << endl; }
    virtual void bar() override final { cout << "Derived1 bar" << endl; }
    };
    
    class Derived2 : public Base {
    public:
    virtual void foo() override final { cout << "Derived2 foo" << endl; }
    virtual void bar() override final { cout << "Derived2 bar" << endl; }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    静态多态
    可以在基类中使用static_cast将this指针转换为子类的指针然后调用该子类的方法。

    // 静态多态===============================================================================================
    template<typename T> 
    class Base {
    public:
      void foo() { static_cast<T *>(this)->internal_foo(); }
      void bar() { static_cast<T *>(this)->internal_bar(); }
    };
    
    class Derived1 : public Base<Derived1> {
    public:
      void internal_foo() { cout << "Derived1 foo" << endl; }
      void internal_bar() { cout << "Derived1 bar" << endl; }
    };
    
    class Derived2 : public Base<Derived2> {
    public:
      void internal_foo() { cout << "Derived2 foo" << endl; }
      void internal_bar() { cout << "Derived2 bar" << endl; }
    };
    
    template <typename T> 
    void foo(Base<T> &obj) { obj.foo(); }
    
    template <typename T> 
    void bar(Base<T> &obj) { obj.bar(); }
    
    int main(int argc, char** argv) {
      Derived1 d1;
      Derived2 d2;
    
      foo(d1);
      foo(d2);
      bar(d1);
      bar(d2);
    
      return 0;
    }
    
    • 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

    利用main函数进行测试得到:

    int main(int argc, char** argv) {
      Derived1 d1;
      Derived2 d2;
    
      foo(d1);
      foo(d2);
      bar(d1);
      bar(d2);
    
      cout << "==================================\n";
      t::Derived1 t1;
      t::Derived2 t2;
      t::Base *p = &t1;
      t::Base *q = &t2;
      p->foo();
      q->foo();
      p->bar();
      q->bar();
     
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述

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

    // 其实这个也类似于代码复用
    
    template<typename T> 
    class Base {
    public:
      static int getObjCnt() { return cnt; }
    
    protected:
      static int cnt ;
    };
    
    
    // 对cnt进行初始化,static修饰的成员不能再类内初始化
    // 非const的static变量如果在当前文件初始化的话, 编译器会将它翻译成一个强符号.如果在类中初始化, 当这个类作为头文件的一员被引入到多个文件后, 就会产生多个名字相同的强符号, 链接时就会出现重复定义的错误.
    template <typename T> 
    int Base<T>::cnt = 0;
    
    class Derived1 : public Base<Derived1> {
    public:
      Derived1() { cnt++; }
    };
    
    class Derived2 : public Base<Derived2> {
    public:
      Derived2() { cnt++; }
    };
    
    • 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

    用main函数进行测试

    int main(int argc, char** argv) {
      Derived1 d11, d12, d13;
      Derived2 d21, d22;
    
      cout << "Derived1::getObjCnt() = " << Derived1::getObjCnt() << endl;
      cout << "Derived2::getObjCnt() = " << Derived2::getObjCnt() << endl;
    
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    结果如下
    在这里插入图片描述

    性能测试

    参考链接
    如果可以使用静态多态来替换动态多态的话,CRTP确实能提升程序的性能,但说到底CRTP有可能省下的只是函数调用的开销,如果说函数体是计算密集型的,这时候函数调用的开销几乎可以忽略不计,那么这时候CRTP就不一定是最好的选择,在实际的场景中,具体使用哪种方案还是要具体情况具体分析。

    总结

    如果不是对性能有极致追求,谨慎使用!

  • 相关阅读:
    mysql5.7的安装
    数据在内存中的存储(2)
    E049-论坛漏洞分析及利用-针对bwapp进行web渗透测试的探索
    python的if __name__ == “__main__“语法错误SyntaxError: invalid syntax
    C++标准异常库
    git提交代码到远程仓库
    SpringBoot中使用Redis实现分布式锁
    数据模型是什么,有哪些常见的分析方法? 财务、人力、运营等数据分析人士必看!(内附2000+套数据可视化模版)
    阻塞式队列
    在golang中使用protoc
  • 原文地址:https://blog.csdn.net/qq_43964318/article/details/126875363