探索c++的對象模型(一):單繼承和多繼承的對象模型

虛函數表就是通過一塊連續內存來保存虛函數的地址

單繼承虛函數的對象模型

class A
{
public:
     virtual void func1()
     {
           printf("A::func1\n");
     }
     virtual void func2()
     {
           printf("A::func2\n");
     }
public:
     int _a;
};
class B
{
public:
     virtual void func1()
     {
           printf("B::func1\n");
     }
     virtual void func3()
     {
           printf("B::func3\n");
     }
     virtual void func4()
     {
           printf("B::func4\n");
     }
public:
     int _b;
};
typedef void(*FUNC) ();
void PrintVTable(int *VTable)
{
     size_t i = 0;
     for (; VTable[i] != NULL; i++)
     {
           printf("第%d個虛函數:0x%p---->", i, VTable[i]);
           //將取出來的地址強轉爲函數指針調用函數
           FUNC f = (FUNC)VTable[i];
           f();
           printf("\n");
     }
}
int main()
{
     A a;
     B b;
     //打印兩個類的虛表
     PrintVTable((int *)*((int *)(&a)));
     PrintVTable((int *)*((int *)(&b)));
     system("pause");
     return 0;
}

因爲虛表裏存的是一個一個的函數指針,而虛標的地址又存在類的最上面的四個字節,所以呀只要我們將最上面的四個字節裏的地址取出來然後去根據這個地址來找到虛表,然後我門可以將虛表看做一個數組,數組裏的每一個元素都是地址(函數指針),那麼我們將這個數組打印即可


那麼這個打印虛表的難點爲這句
 PrintVTable((int *)*((int *)(&a)))
此句就時將類的地址取出來然後強轉爲(int*),只將頭上4個字節取出,在解引用,但是解引用了之後的值是一個int型的數據,那我們再將它強轉爲(int *)然後根據地址找虛表即可

那麼我們可以發現此代碼是在32位下運行的,那麼我們怎麼寫一份代碼既可以在32位下運行也可以在64位運行呢
 PrintVTable((int **)*((int **)(&a)))
int**解引用了之後,在多少位下指針就是多少位


我們來一下內存和打印的對應
我們可以看到在原來A類的func1函數的地方B進行了虛函數的重寫


我門並沒有看到func3和func4 在虛表中這個編譯器的一個bug,但其實它是真實存在的,通過打印我們也能看出來

我們再來手動的梳理一下來畫張圖


多繼承虛函數的對象模型

class A
{
public:
     virtual void func1()
     {
           printf("A::func1\n");
     }
     virtual void func2()
     {
           printf("A::func2\n");
     }
public:
     int _a;
};
class B
{
public:
     virtual void func1()
     {
           printf("B::func1\n");
     }
     virtual void func2()
     {
           printf("B::func2\n");
     }
public:
     int _b;
};
class C :public A,public B
{
     virtual void func1()
     {
           printf("C::func1\n");
     }
     virtual void func3()
     {
           printf("C::func3\n");
     }
public:
     int _c;
};
typedef void(*FUNC) ();
void PrintVTable(int *VTable)
{
     size_t i = 0;
     printf("虛表地址:%p\n", VTable);
     for (; VTable[i] != NULL; i++)
     {
           printf("第%d個虛函數:0x%p---->", i, VTable[i]);
           //將取出來的地址強轉爲函數指針調用函數
           FUNC f = (FUNC)VTable[i];
           f();
     }
     printf("\n\n");
}
int main()
{
     C c;
     //打印兩個類的虛表
     PrintVTable((int *)*((int *)(&c)));
     PrintVTable((int *)*((int *)(&c) + sizeof(A) / 4));
     system("pause");
     return 0;
}
然後我們來看一下多繼承的模型

以下幾點需注意
  1. 當子類裏有對兩個父類都重寫的虛函數時,都會進行覆蓋重寫
  2. 子類會繼承父類的虛表
  3. 子類的沒有重寫的函數放在(先繼承哪個父類)就放在那個父類的虛表中,(以上代碼子類先繼承了A,所以子類裏其他的函數都放在A的虛表裏)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章