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