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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM