轉載自:https://blog.csdn.net/qq_36359022/article/details/818702
C++虛函數表是支撐C++多態的重要技術,它是C++動態綁定技術的核心。
一、內存分布
假設有一個基類ClassA,一個繼承了該基類的派生類ClassB,並且基類中有虛函數,派生類實現了基類的虛函數。
我們在代碼中運用多態這個特性時,通常以兩種方式起手:
(1) ClassA *a = new ClassB();
(2) ClassB b; ClassA *a = &b;
以上兩種方式都是用基類指針去指向一個派生類實例,區別在於第1個用了new關鍵字而分配在堆上,第2個分配在棧上。
請看上圖,不同兩種方式創建方式僅僅影響了派生類對象實例存在的位置。
以左圖為例,ClassA *a是一個棧上的指針。該指針指向一個在堆上實例化的子類對象。基類如果存在虛函數,那么在子類對象中,除了成員函數與成員變量外,編譯器會自動生成一個指向**該類的虛函數表(這里是類ClassB)**的指針,叫作虛函數表指針。通過虛函數表指針,父類指針即可調用該虛函數表中所有的虛函數。
二、類的虛函數表和類實例的虛表指針
首先在不考慮繼承的情況下。如果一個類有虛函數,那么該類就有一個虛函數表,這個虛函數表是屬於類的,所有該類的實例化對象都會有一個虛函數表指針指向該類的虛函數表。
從第一部分的圖中我們也能看到,一個類的實例要么在堆上,要么在棧上,也就是說一個類可以有很多個實例。但是,一個類只有一個虛函數表。在編譯時,一個類的虛函數表就確定了,這也是為什么它放在了只讀數據段中。
調用的過程如下:
例如:
A* p = new B()
p->func()
實際的調用過程是:
vt = p->vptr; // 獲取虛表
vf = vt[0]; //獲取函數地址,因為這里只有一個虛函數,所以fun在虛表中的標號為0
p->vf()
三、普通繼承/多重繼承下的虛函數表
在有繼承情況下,只要基類有虛函數,子類不論實現或沒實現,都有虛函數表。
子類中與基類虛函數同名的函數,也會自動加上virtual。
首先,子類會繼承基類的虛函數表,如果重寫了基類的虛函數會更新虛函數表。如果沒有重寫任何基類的虛函數,那么子類和基類的虛函數表是內容是一致的。
但是,基類的虛函數表和子類的虛函數表不是同一個表。
四、多繼承下的虛函數表(同時繼承多個基類)
多繼承是指一個類同時繼承了多個基類,假設這些基類都有虛函數,也就是說每個基類都有虛函數表,那么該子類的邏輯結果和虛函數表是什么樣子呢?
在多繼承情況下,有多少個基類就有多少個虛函數表指針,前提是基類要有虛函數才算上這個基類。
注意:
1.子類虛函數會覆蓋每一個父類的每一個同名虛函數。
2.父類中沒有的虛函數而子類有,填入第一個虛函數表中,且用父類指針是不能調用。
3.父類中有的虛函數而子類沒有,則不覆蓋。僅子類和該父類指針能調用。