C/C++—— C++編譯器是如何實現多型

readyao發表於2016-04-04

關於C++中多型的基礎知識參考部落格:
C/C++—— 對多型現象的理解

C++中多型的實現原理

1.當類中宣告虛擬函式時,編譯器會在類中生成一個虛擬函式。
2.虛擬函式表是一個儲存類成員函式指標的資料結構。
3.虛擬函式表是由編譯器自動生成與維護的。
4.virtual成員函式會被編譯器放入虛擬函式表中。
5.存在虛擬函式時,每個物件中都有一個指向虛擬函式表的指標(vptr指標)。

虛擬函式表

虛擬函式表2

虛擬函式表3

說明1:
通過虛擬函式表指標VPTR呼叫重寫函式是在程式執行時進行的,因此需要通過定址操作才能確定真正應該呼叫的函式。而普通成員函式是在編譯時就確定了呼叫的函式。在效率上,虛擬函式的效率要低很多。
說明2:
出於效率考慮,沒有必要將所有成員函式都宣告為虛擬函式

證明vptr指標的存在

定義一個沒有虛擬函式類,檢視該類的大小。然後再將其中的函式宣告為虛擬函式,再檢視該類的大小。結果發現該類大小增大了,可以知道增加了一個指標變數,該變數就是vptr指標。

沒有虛擬函式:

#include <iostream>
using namespace std;

class Parent{
    public:
        Parent(int a = 0){
            this->a = a;
        }
        //virtual void print()
        void print()
        {
            cout << "Parent, a = " << a << endl;
        }
    private:
        int a;
};


int main()
{
    cout << "沒有虛擬函式: ";
    cout << "sizeof(Parent) = " << sizeof(Parent) << endl;

    return 0;
}

輸出為:
沒有虛擬函式: sizeof(Parent) = 4

有虛擬函式:

#include <iostream>
using namespace std;

class Parent{
    public:
        Parent(int a = 0){
            this->a = a;
        }
        virtual void print()
        {
            cout << "Parent, a = " << a << endl;
        }
    private:
        int a;
};

int main()
{
    cout << "有虛擬函式: ";
    cout << "sizeof(Parent) = " << sizeof(Parent) << endl;
    return 0;
}

輸出為:
有虛擬函式: sizeof(Parent) = 16
這裡要解釋一下為什麼變為16了。。因為我電腦是64的linux系統,指標變數大小為8位元組,又因為位元組對齊的原因總的大小變為了16位元組。

可以去參考部落格:
C/C++——基本資料型別的大小並且sizeof(int *) = 8
C/C++—— 記憶體位元組對齊規則

物件中的VPTR指標什麼時候被初始化?

1.物件在建立的時,由編譯器對VPTR指標進行初始化
2.只有當物件的構造完全結束後VPTR的指向才最終確定
3.父類物件的VPTR指向父類虛擬函式表
4.子類物件的VPTR指向子類虛擬函式表

當定義一個父類物件的時候比較簡單,因為父類物件的VPTR指標直接指向父類虛擬函式表。
但是當定義一個子類物件的時候就比較麻煩了,因為構造子類物件的時候會首先呼叫父類的建構函式然後再呼叫子類的建構函式。當呼叫父類的建構函式的時候,此時會建立Vptr指標(也可以認為Vptr指標是屬於父類的成員,所以在子類中重寫虛擬函式的時候virtual關鍵字可以省略,因為編譯器會識別父類有虛擬函式,然後就會生成Vptr指標變數),該指標會指向父類的虛擬函式表;然後再呼叫子類的建構函式,此時Vptr又被賦值指向子類的虛擬函式表。

上面的過程是Vptr指標初始化的過程。
這是因為這個原因,在建構函式中呼叫虛擬函式不能實現多型。
見下一篇部落格:在建構函式中呼叫虛擬函式能實現多型嗎

相關文章