跟我一塊兒學習C++虛函數--第五篇

       在前一篇,咱們討論了在多重繼承狀況下,具備虛函數的類的內存佈局狀況。本篇將進一步探索在多重虛擬繼承狀況下的內存佈局狀況。
    
      在討論多重虛擬繼承前,咱們先對《 淺析GCC下C++多重繼承 & 虛擬繼承的對象內存佈局》中的例子進行擴充。先看看帶有虛函數的單一虛擬繼承狀況下類的內存佈局。
        
        先看例子:
#include <iostream>
using namespace std;

class Top
{
public:
    virtual void x(){cout << "top x" << endl;}
    virtual void print0(){cout << "top print" << endl;}
public:
    int a;
};

class Left:virtual public Top
{
public:
    virtual void x(){cout << "left x" << endl;}
    virtual void print1(){cout << "left print" << endl;}
public:
    int b;
};


 int main()
 {
    /*first part*/
    cout << sizeof(Top) << "\t" << sizeof(Left) << endl;//輸出:8  16
    Left *left = new Left();
    cout << left << " " << &left->b << " " << &left->a << endl; 
    //輸出:0x8517008 0x851700c 0x8517014

    /*second part*/
    typedef void (*Func)(void);
    Func pFunc;
    pFunc = (Func)*((int *)*(int *)(left));
    pFunc();//輸出:left x
    pFunc = (Func)*((int *)*(int *)(left)+1);
    pFunc();//輸出:left print
//  pFunc = (Func)*((int *)*(int *)(left)+2);
//  pFunc();//段錯誤

    /*third part*/
    pFunc = (Func)*((int *)*((int *)(left)+2));
    pFunc();//輸出:left x
    pFunc = (Func)*((int *)*((int *)(left)+2)+1);
    pFunc();//輸出:top print
//  pFunc = (Func)*((int *)*((int *)(left)+2)+2);
//  pFunc();//段錯誤

    delete left;
    return 0;
 }

        從輸出狀況咱們能夠得知如下信息: 在具備虛函數的單一虛擬繼承時,子類中會有多個虛指針以及多個虛函數表。咱們能夠用以下圖來表示對應的內存佈局:
        

        虛擬繼承時,編譯器會在子類中多插入一個父類的虛指針。
        
         下面咱們正式討論多重繼承時的狀況,請看例子:
#include <iostream>
using namespace std;

class Top
{
public:
    virtual void x(){cout << "top x" << endl;}
    virtual void print0(){cout << "top print" << endl;}
public:
    int a;
};

class Left:virtual public Top
{
public:
    virtual void y(){cout << "left y" << endl;}
    virtual void print1(){cout << "left print" << endl;}
public:
    int b;
};

class Right:virtual public Top
{
public:
    virtual void z(){cout << "right z" << endl;}
    virtual void print2(){cout << "right print" << endl;}
public:
    int c;
};

class Bottom : public Left, public Right
{
public:
    virtual void y(){cout << "bottom y" << endl;}
    virtual void z(){cout << "bottom z" << endl;}
    virtual void print3(){cout << "bottom print" << endl;}
public:
    int d;
};

 int main()
 {
    /*first part*/
    cout << sizeof(Top) << "\t" << sizeof(Left) << "\t" << sizeof(Right) << "\t" << sizeof(Bottom) << endl;
    //輸出:8       16      16      28
    Bottom *b = new Bottom();
    cout << b << " " << &b->b << " " << &b->c << " " << &b->d << " " << &b->a << endl;
    //輸出:0x9814008 0x981400c 0x9814014 0x9814018 0x9814020
	
    /*second part*/
    typedef void (*Func)(void);
    Func pFunc;
    pFunc = (Func)*((int *)*(int *)(b));
    pFunc();//輸出:bottom y 
    pFunc = (Func)*((int *)*(int *)(b)+1);
    pFunc();//輸出:left print
    pFunc = (Func)*((int *)*(int *)(b)+2);
    pFunc();//輸出:bottom z
    pFunc = (Func)*((int *)*(int *)(b)+3);
    pFunc();//輸出:bottom print
//  pFunc = (Func)*((int *)*(int *)(b)+4);
//  pFunc();//段錯誤

    /*third part*/
    pFunc = (Func)*((int *)*((int *)(b)+2));
    pFunc();//輸出:bottom z
    pFunc = (Func)*((int *)*((int *)(b)+2)+1);
    pFunc();//輸出:right print
//  pFunc = (Func)*((int *)*((int *)(b)+2)+2);
//  pFunc();//段錯誤

    /*fourth part*/
    pFunc = (Func)*((int *)*((int *)(b)+5));
    pFunc();//輸出:top x
    pFunc = (Func)*((int *)*((int *)(b)+5)+1);
    pFunc();//輸出:top print
//  pFunc = (Func)*((int *)*((int *)(b)+5)+2);
//  pFunc();//段錯誤

    delete b;
    return 0;
 }


 對於上面的例子,咱們分爲四部分進行講解。

第一部分:多重虛擬繼承狀況下,對象自己(除虛函數表外)的內存佈局。

        從代碼中first part的輸出狀況來看,結合本文最開始對單一虛擬繼承的討論,Top、Left和Right的大小很容易理解,至於Bottom類的大小,若是你看過參考文獻一,那麼也很容易理解。在虛擬繼承的狀況下,Bottom類只包含了一次Top類中的a成員變量,所以總共有4個int成員變量,爲16字節。再加上三個虛指針,即爲28字節。

       從輸出的Bottom對象中成員變量地址狀況,咱們能夠用以下圖來表示內存佈局:
        
        
        簡單地說,多重虛擬繼承時,子類會有一個祖父類的存在,包括祖父類的成員變量和虛函數。

第二部分:多重虛擬繼承狀況下,主要虛函數表的內存佈局。

        從代碼的second part的輸出狀況來看,咱們能夠用下圖來表示主要虛函數表的內存佈局:
      
       
第三部分:多重虛擬繼承狀況下,次要虛函數表的內存佈局。

        從代碼的third part的輸出狀況來看,咱們能夠用下圖來表示次要虛函數表的內存佈局:
        

第四部分:多重虛擬繼承狀況下,次要祖父虛函數表的內存佈局。

        從代碼的fourth part的輸出狀況來看,咱們能夠用下圖來表示祖父虛函數表的內存佈局:
        
        
         對比前一篇多重繼承狀況下類的內存佈局,咱們能夠發現: 在多重虛擬繼承狀況下,祖父類只會在子類中具備一份存在,包括祖父類的成員變量和虛函數。子類中具備一個主要虛函數表和多個次要虛函數。主要虛函數表包括父類1中被覆蓋的虛函數、父類1中沒有被覆蓋的虛函數、其餘父類中被覆蓋的虛函數以及子類獨有的虛函數。次要虛函數表中包括被子類覆蓋的虛函數以及未被子類覆蓋的虛函數。

 
         本系列(完)。
        
參考文獻:


          2. 《深度探索C++對象模型》
相關文章
相關標籤/搜索