C++中單繼承、多繼承、菱形繼承、虛擬菱形繼承的對象模型

以下測試均在VS2013中進行。

1.帶虛函數的單繼承內存佈局
看下面一段例子

#include <iostream>
using namespace std;

class A
{
public:
    virtual void test1()
    {
        cout << "A::test1" << endl;
    }
    virtual void test3()
    {
        cout << "A::test3" << endl;
    }
    int _a;
};

class B:public A
{
public:
    virtual void test1()
    {
        cout << "B::test1" << endl;
    }
    virtual void test2()
    {
        cout << "B::test2" << endl;
    }
    int _b;
};

typedef void(*PF)();
void print(B *p)
{
    PF* tmp = (PF*)*(int*)p;
    while (*tmp)
    {
        (*tmp)();
        tmp++;
    }
}

int main()
{
    B t;
    cout << sizeof(t) << endl;
    t._a = 1;
    t._b = 2;
    print(&t);
    return 0;
}

這裏寫圖片描述
首先用sizeof求出對象的大小爲12個字節,其中前4個字節爲虛函數指針,它指向一個虛函數表(也就是一個函數指針數組),其中存放的是虛函數的地址,最後一項爲零。
那麼虛函數表中是哪幾個函數呢?
既然虛函數表中是函數的地址,那麼只要我們拿到函數的地址就可以進行調用,根據輸出結果即可判定是哪個函數。我們藉助了一個輔助函數print,具體實現間源代碼。
print函數的形參是用來接收對象的地址的。
*(int*)p: 將p轉換爲整形地址,並取出對象的前4個字節中的內容,即虛表指針,但此 時值爲整形

(PF*)*(int*)p:由於數組中值的類型爲PF,在將上一步的續表指針強轉爲PF*(可類比int數組,數組中值的類型爲int,則指針類型爲int*)

通過調用print函數顯示結果如下圖
這裏寫圖片描述
可以看出,派生類先把基類的虛函數表繼承下來,如果重寫了基類的某個虛函數,則將虛函數表中這個函數該爲派生類的(本例子爲test1函數),再把派生類自己的虛函數加入到虛函數表中。

2.帶虛函數的多繼承內存佈局、

class A
{
public:
    virtual void test1()
    {
        cout << "A::test1" << endl;
    }
    virtual void test3()
    {
        cout << "A::test3" << endl;
    }
    int _a;
};

class B
{
public:
    virtual void test1()
    {
        cout << "B::test1" << endl;
    }
    virtual void test2()
    {
        cout << "B::test2" << endl;
    }
    int _b;
};

class C :public A, public B
{
public:
    virtual void test1()
    {
        cout << "C::test1" << endl;
    }
    virtual void test3()
    {
        cout << "C::test3" << endl;
    }
    virtual void test4()
    {
        cout << "C::test4" << endl;
    }
    int _c;

};

這裏寫圖片描述

這裏寫圖片描述
可以看出,在本例中多繼承時有兩個虛函數指針,並且繼承時先聲明的類位於內存中最前面,當派生類中有自己的虛函數時它放在第一張虛表中。

3.帶虛函數的菱形繼承內存佈局

這裏寫圖片描述

這裏寫圖片描述

從圖中可以看出,菱形繼承就是單繼承與多繼承的結合。

4.虛擬繼承(不帶虛函數)

#include <iostream>
using namespace std;
class A
{
public:
    void fun()
    {
        cout << "A::fun" << endl;
    }
    int _a;
};
class B :virtual public A
{
public:
    int _b;
};

int main()
{
    B t;
    cout << sizeof(t) << endl;
    t._a = 1;
    t._b = 2;
    return 0;
}

這裏寫圖片描述
可以看到,對象前四個字節依舊是一個指針,它指向的是一個偏移量表格,表格中的第一項是派生類自身部分相對於指針的偏移量,第二項是派生類對象中基類部分相對於指針的偏移量,剛好是8個字節。
此時,基類部分位於後半部分,而派生類部分位於前半部分。

5.菱形虛擬繼承
這裏寫圖片描述

#include <iostream>
#include <string>
using namespace std;

class A
{
public:
    virtual void test1()   //B1、B2、C均類重寫
    {
        cout << "A::test1" << endl;
    }
    virtual void test3()  //A類獨有的函數
    {
        cout << "A::test3" << endl;
    }
    int _a;
};

class B1: virtual public A
{
public:
    virtual void test1()
    {
        cout << "B1::test1" << endl;
    }
    virtual void test2() //B1獨有的函數
    {
        cout << "B1::test2" << endl;
    }
    int _b1;
};
class B2 : virtual public A
{
public:
    virtual void test1()
    {
        cout << "B2::test1" << endl;
    }
    virtual void test4()  //C重寫的函數
    {
        cout << "B2::test4" << endl;
    }
    int _b2;
};

class C :public B1, public B2
{
public:
    virtual void test1()
    {
        cout << "C::test1" << endl;
    }
    virtual void test5()    //C獨有的函數
    {
        cout << "C::test5" << endl;
    }
    virtual void test4()
    {
        cout << "C::test4" << endl;
    }
    int _c;

};

typedef void(*PF)();
void print(A *p, const string& str)
{
    cout << str << endl;
    PF* tmp = (PF*)*(int*)p;
    while (*tmp)
    {
        (*tmp)();
        tmp++;
    }
    cout << endl;
}

void print(B1 *p, const string& str)
{
    cout << str << endl;
    PF* tmp = (PF*)*(int*)p;
    while (*tmp)
    {
        (*tmp)();
        tmp++;
    }
    cout << endl;
}

void print(B2 *p, const string& str)
{
    cout << str << endl;
    PF* tmp = (PF*)*(int*)p;
    while (*tmp)
    {
        (*tmp)();
        tmp++;
    }
    cout << endl;
}

int main()
{
    C t;
    cout << sizeof(t) << endl;
    t._a = 1;
    t._b1 = 2;
    t._b2 = 3;
    t._c = 4;
    B1 *b1 = &t;
    B2 *b2 = &t;
    A *a = b1;

    print(a, "A");
    print(b1, "B1");
    print(b2, "B2");
    cout << endl;
    return 0;
}

這裏寫圖片描述

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