C++ 虛擬函式的內部實現
單繼承的情況下
若類有虛擬函式,則在建構函式的時候編譯器會自動為類的例項(物件)在其記憶體的首部(0地址偏移處)增添一個虛擬函式表指標vfptr,指向該類的虛擬函式表。虛擬函式表中會存放該類所有的虛擬函式地址,普通函式則不會被放入其中。如果是子類重寫了父類的虛擬函式,那麼在建立虛擬函式表的時候被重寫的虛擬函式的地址被替換成了子類的虛擬函式。
而使用父類指標BaseClass* base指向一個子類物件DerivedClass時,當呼叫虛擬函式virtualFunc的時候,其實際執行過程是base->vfptr->virtualFunc , 這樣就實現了父類指標呼叫實際子類的成員函式。
多繼承的情況下
普通的多繼承
class Base { int a; int b; public: void virtual VirtualFunction(); }; class DerivedClass1: public Base { int c; public: void virtual VirtualFunction(); }; class DerivedClass2 : public Base { int d; public: void virtual VirtualFunction(); }; class DerivedDerivedClass : public DerivedClass1, public DerivedClass2 { int e; public: void virtual VirtualFunction(); };
DerivedDerivedClass的記憶體分佈中,會有兩個vfptr分別指向兩個虛擬函式表(DerivedClass1,DerivedClass2)
同時會存在兩份Base類的成員a,b:
虛繼承
若改動如下:
class DerivedClass1: virtual public Base
...
class DerivedClass2 : virtual public Base
...
則DerivedClass1和DerivedClass2的記憶體分佈就已經有變化了。
指向virtual base Base的vfptr以及虛基類Base的成員變數被放在了記憶體的最後部分:
即額外增加了一個虛擬函式表指標vbptr指向派生類的虛擬函式表。
並且最終DerivedDerivedClass的記憶體分佈如下:
、
可見,虛繼承是通過增加額外的虛擬函式表指標來達到保證基類的資料只有一份的特性。
虛擬函式表什麼時候初始化?在記憶體哪一塊?
虛擬函式表指標vptr是每個物件例項都有一份的,其指向的虛擬函式表是屬於類的,每個類都有自己單獨的唯一一份虛擬函式表,放置在只讀資料段.rodata。虛擬函式表是由一個個的虛擬函式指標組成的。.rodata只讀資料段裡面存放的還有由const修飾的只讀資料。
虛擬函式表中的偏移量有何作用?
可以看到虛擬函式表中,DerivedClass1中的20表明它的虛指標{vbptr}離虛基表指標{vfptr}的距離。同理DerivedClass2的12。最後一張表是虛基表,-20指明瞭它對應的虛指標{vfptr}在記憶體中的偏移。
這些偏移量的作用暫時還不清楚其具體的應用過程,希望有同學可以互相交流和補充。
參考:
探索C++虛擬函式在g++中的實現
https://www.cnblogs.com/senior-engineer/p/7832915.html
C++類記憶體分佈
http://www.cnblogs.com/jerry19880126/p/3616999.html