zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    多重背包POJ1276不要求恰好装满 poj1014多重背包恰好装满
    哈理工1053完全背包
    求最小公倍数与最大公约数的函数
    Bus Pass ZOJ 2913 BFS 最大中取最小的
    POJ 3624 charm bracelet 01背包 不要求装满
    HavelHakimi定理(判断一个序列是否可图)
    z0j1008Gnome Tetravex
    ZOJ 1136 Multiple BFS 取模 POJ 1465
    01背包 擎天柱 恰好装满 zjut(浙江工业大学OJ) 1355
    zoj 2412 水田灌溉,求连通分支个数
  • 原文地址:https://www.cnblogs.com/fresky/p/3504241.html
Copyright © 2011-2022 走看看