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

posted on 2014-01-03 20:22  fresky  阅读(3549)  评论(1编辑  收藏

导航