1.關于vptr和vtbl
之前的學習已經了解到c++多態主要靠虛函數實現,如果說c++的class在實現上相比c的struct有什么開銷的話,那么虛函數表(vtbl)的維護和每個對象實例里虛表指針(vptr)將是比較明顯的開銷。
對于如下三個類
class A {public: virtual void vfunc1() { cout << "A::vfunc1/n"; } virtual void vfunc2() { cout << "A::vfunc2/n"; } void func1() { cout << "A::func1/n"; } void func2() { cout << "A::func2/n"; }};class B : public A {public: virtual void vfunc1() { cout << "B::vfunc1/n"; } void funcb() { cout << "B::funcb/n"; }};class C : public B {public: virtual void vfunc1() { cout << "C::vfunc1/n"; } void funcc() { cout << "C::funcc/n"; }};非虛成員函數:A::func1(),A::func2(),B::funcb(),C::funcc()會單獨在內存里存一份
虛成員函數:A::vfunc1(),A::vfunc2(),B::vfunc1(),C::vfunc1()也會單獨存一份,但是這四個虛函數會由虛函數表來記錄,由于這個例子里有三個類,因此內存里會有三份虛函數B::vfunc1(),A::vfunc2(),表,我們假設它們為A,B,C表。 A表里會有兩個指針,分別指向A::vfunc1(),A::vfunc2()的地址,B表里兩個指針,分別指向B::vfunc1(),A::vfunc2(),同理,C表里的指針指向C::vfunc1(),A::vfunc2()。
對于用基類指針new子類的情況:A *pa = new B; 這個實例對象里放的也是B類對應的虛函數表,因為編譯器做了個向上轉型(upcasting)。
其實理解了虛函數表在內存的形式后,調用虛函數的代碼可以這么表示: (*(pa->vptr)[n])(pa) 因為第一個參數肯定是*this。
學習群里u6th9d當時給我們提供了一些虛函數相關考驗題
Cat cat("cat"); Dog dog("dog"); Animal* pcat = &cat; Animal* pdog = &dog; std::cout << "L01: "; pcat->say(); std::cout << "L02: "; pdog->say(); std::cout << "L03: "; cat.say(); std::cout << "L04: "; dog.say(); void* tmp = ((void**)pcat)[0]; ((void**)pcat)[0] = ((void**)pdog)[0]; ((void**)pdog)[0] = tmp; std::cout << "L05: "; pcat->say(); std::cout << "L06: "; pdog->say(); std::cout << "L07: "; cat.say(); std::cout << "L08: "; dog.say();之前也提到,成員對象的第一個內容是虛表,因此中間那段就是交換了對象里的虛表指針,使得*pcat里的vptr指向Dog類的vtbl,*pdog的vptr指向Cat類的vtbl,結果:
L01: cat miaomiao~~L02: dog wangwang~~L03: cat miaomiao~~L04: dog wangwang~~L05: cat wangwang~~L06: dog miaomiao~~L07: cat miaomiao~~L08: dog wangwang~~L05和L06確實交換了,但是L07和L08并沒有表,我當時沒弄清楚,續表指針不是都變了嗎?為什么調用的還是原來的函數呢?原因在于對動態綁定的理解
2.動態綁定
為了C++的多態性,是有動態綁定和靜態綁定這兩種說法的:
靜態綁定:綁定的對象是靜態類型,也就是編譯期就能決定的,是確定的,不會更改的,比如 A a; a的內容雖然會在運行期發生改變,但是a就是a,這點是不會變的。
動態綁定:綁定的對象是動態類型,動態類型就是指在編譯期無法決定的,因為它可能在運行期發生改變,比如指針:A* pa; pa可以在運行時重新指向其他對象,或者轉型指向B類或者C類。
這邊文章很好地總結了靜態綁定和動態綁定:https://www.oschina.net/question/54100_20313
通過vptr和vtbl實現虛函數是基于動態綁定的,因此基于指針調用的虛函數pcat,pdog調用的函數會隨著虛表指針的改變發生改變,但是用過普通對象直接調用,例如L07和L08,這是靜態綁定,在編譯期就已經決定了要調用的函數,因此不會改變。
新聞熱點
疑難解答
圖片精選