• C++的静态分发(CRTP)和动态分发(虚函数多态)的比较


    虚函数是C++实现多态的工具,在运行时根据虚表决定调用合适的函数。这被称作动态分发。虚函数很好的实现了多态的要求,但是在运行时引入了一些开销,包括:

    1. 对每一个虚函数的调用都需要额外的指针寻址
    2. 虚函数通常不能被inline,当虚函数都是小函数时会有比较大的性能损失
    3. 每个对象都需要有一个额外的指针指向虚表

    所以如果是一个对性能要求非常严格的场合,我们就需要用别的方式来实现分发,这就是今天这篇博客的主角CRTP

    CRTP通过模板实现了静态分发,会带来很多性能的好处。可以参见The cost of dynamic (virtual calls) vs. static (CRTP) dispatch in C++看一下性能比较。

    下面简单介绍一下怎么实现CRTP。

    首先看我们的父类:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    template<typename Derived>  class Parent 
    {
    public:
        void SayHi()
        {
            static_cast<Derived*>(this)->SayHiImpl();
        }
    private:
        void SayHiImpl() // no need if no need the default implementation
        {
            cout << "hi, i'm default!" << endl;
        }
    };

    它是一个模板类,它有一个需要接口函数是SayHi。它有一个默认实现在SayHiImpl中。

    再来看看它的子类:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    class ChildA :public Parent<ChildA>
    {
    public:
        void SayHiImpl()
        {
            cout << "hi, i'm child A!" << endl;
        }
    };
    
    class ChildB :public Parent<ChildB>
    {
    };

    我们可以看到ChildAChildB继承自这个模板类,同时ChildA有自己的实现。

    在写一个普通的用虚函数实现分发的类:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class ParentB
    {
    public:
        void virtual SayHi()
        {
            cout << "hi, i'm default!" << endl;
        }
    };
    
    class ChildC : public ParentB
    {
    public:
        void SayHi()
        {
            cout << "hi, i'm ChildC!" << endl;
        }
    };
    
    class ChildD : public ParentB
    {
    };

    然后是调用这两个父类的函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    template<typename Derived> void CRTP(Parent<Derived>& p)
    {
        p.SayHi();
    }
    
    void Dynamic(ParentB& p)
    {
        p.SayHi();
    }

    再来看看main函数:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    int _tmain(int argc, TCHAR* argv[])
    {
    
        ChildA a;
        CRTP(a);
        cout << "size of ChildA: " << sizeof(a) << endl;
    
        ChildB b;
        CRTP(b);
        cout << "size of ChildB: " << sizeof(b) << endl;
    
        ChildC c;
        Dynamic(c);
        cout << "size of ChildC: " << sizeof(c) << endl;
    
        ChildD d;
        Dynamic(d);
        cout << "size of ChildD: " << sizeof(d) << endl;
    
        return 0;
    }

    如果运行这个程序,可以看到如下的输出,可以看到CRTP可以实现和虚函数一样的功能,但是内存大小会有很大优势,关于对象内存可以参见我之前的博客怎么看C++对象的内存结构 和 怎么解密C++的name Mangling

    1
    2
    3
    4
    5
    6
    7
    8
    9
    hi, i'm child A!
    size of ChildA: 1
    hi, i'm default!
    size of ChildB: 1
    hi, i'm ChildC!
    size of ChildC: 4
    hi, i'm default!
    size of ChildD: 4
    Press any key to continue . . .

    完整代码参见gist

  • 相关阅读:
    干货分享!用户级爬虫,怎敢封IP
    一篇就够,网站数据提取,数据使用
    scrapy 解决爬虫IP代理池,数据轻松爬。
    使用代理ip的作用是什么?
    爬虫工作怎样选择代理ip
    爬取VIP视频
    python爬虫数据追加至excel中
    使用geany编辑器时python输出中文问题
    在Windows系统中从终端运行Python程序
    Windows 安装 Python 及配置环境变量
  • 原文地址:https://www.cnblogs.com/fresky/p/3504241.html
Copyright © 2020-2023  润新知