探索c++對象模型

c++中多態的實現

我們都知道,c++中的多態是在虛函數的基礎上實現的,用指向派生類的基類指針調用派生類(或基類)中自己的成員函數。那麼,具體是怎麼實現的呢?

其實它是通過虛函數表來實現的,虛函數表是保存虛函數地址的一張表,若一個類中有虛函數,當程序運行時,編譯器通過在虛函數表中查找相應的虛函數的地址來調用該函數。

 

對象的繼承有如下幾類:

1.單一繼承

2.多重繼承

3.重複繼承(鑽石繼承)

4.虛繼承

下面我們分別來看一下各種繼承的內存佈局:

 

單一繼承

 

wKioL1bv6NSjYaLJAAAOQSh86C0798.png

單一繼承的結構

運行下面這段程序:

#include<iostream>
using namespace std;
typedef void(*PFUN)();
class B
{
public:
     B() :_b(0){}
     virtual void fun1()
     {
          cout << "B::fun1()" << endl;
     }
     virtual void fun2()
     {
          cout << "B::fun2()" << endl;
     }    
private:
     int _b;
};
class D:public B
{
public:
     D() :_d(1){}
     void fun1()
     {
          cout << "D::fun1()" << endl;
     }
     virtual void fun3()
     {
          cout << "D::fun3()" << endl;
     }
private:
     int _d;
};
void PrintVT(int b)//根據虛函數表中的函數地址調用虛函數
{
     PFUN pfun = NULL;
     int *ptr = (int *)b;
     int i = 0;
     while (ptr[i])
     {
          pfun = (PFUN)ptr[i];//將虛函數的地址轉換爲函數指針
          pfun();//用函數指針調用函數
          i++;
     }
}
int main()
{
     D d;
     PrintVT(*(int *)&d);//取出虛函數表的地址
     getchar();
     return 0;
}

 

我們在內存窗口可以看到:(vs2013)

wKioL1bv7tmRYY2YAAAlmvvo1sQ116.png

 

我們可以在內存中看到對象d在內存中的存儲結構如上圖所示,根據對象在內存中的的存儲結構我們可以取出內存中虛函數表的地址,從而拿到虛函數表中的虛函數地址,通過函數指針可以調用各個虛函數。這樣我們就可以知道虛函數表中所存是哪些虛函數的地址。

 

上面程序的功能是調用虛函數表中函數的地址所對應的函數,程序運行結果如下:

 

 wKioL1bv77-y-7qEAAAG9JenhlY093.png

單一繼承的內存佈局是:

wKiom1bv8bKRJHvkAAAIXOKaB6s174.png

                                                                     單一繼承對象模型

 

1)對象地址的最前面放的是虛函數表的地址,然後根據繼承的順序依次放置成員變量的地址。

2)虛表裏按繼承順序先放置派生類從基類繼承來的虛函數的地址,然後是派生類自己的虛函數的地址。

3)滿足覆蓋條件的派生類的虛函數地址覆蓋了基類虛函數的地址。

 

覆蓋:

1)不同作用域(基類和派生類中)

2)函數名相同,參數列表相同,返回值類型相同(協變除外)

3)爲虛函數(virtual)

注:協變是指基類虛函數返回值爲基類類型的指針,派生類虛函數的返回值爲派生類類型的指針

 

多重繼承

wKioL1bwFVfQE7XXAAAmKEMgbj8180.png

      多重繼承結構

運行下面這段程序:

#include<iostream>
using namespace std;
typedef void(*PFUN)();
class B1
{
public:
     B1() :_b1(1){}
     virtual void fun1()
     {
          cout << "B1::fun1()" << endl;
     }
     virtual void fun2()
     {
          cout << "B1::fun2()" << endl;
     }
private:
     int _b1;
};
class B2
{
public:
     B2() :_b2(2){}
     virtual void fun1()
     {
          cout << "B2::fun1()" << endl;
     }
     virtual void fun2()
     {
          cout << "B2::fun2()" << endl;
     }
private:
     int _b2;
};
class D:public B1,public B2
{
public:
     D() :_d(3){}
     void fun1()
     {
          cout << "D::fun1()" << endl;
     }
     virtual void fun3()
     {
          cout << "D::fun3()" << endl;
     }
private:
     int _d;
};
void PrintVT(int b)
{
     PFUN pfun = NULL;
     int *ptr = (int *)b;
     int i = 0;
     while (ptr[i])//虛函數表結束標誌爲0,當ptr[i]爲0時,循環結束
     {
          pfun = (PFUN)ptr[i];
          pfun();
          i++;
     }
}
//調用虛表中函數的方法與單一繼承一致
int main()
{
     D d;
     PrintVT(*(int *)&d);//拿到第一個虛表的地址
     cout << "---------" << endl;
     PrintVT(*((int *)&d+2));//拿到第二個虛表的地址
     getchar();
     return 0;
}

我們在內存窗口可以看到:(vc++6.0)

wKiom1bwFsLj-RjHAAApBdG6XFQ907.png

程序運行結果如下:

wKiom1bwFvHTbPiwAAAQKLwDttk580.png

上面是第一個虛表中虛函數的調用,下面是第二個。

根據內存分佈,多重繼承的內存佈局爲:

wKiom1bwGieh-K14AAAWwoI1e84066.png

多重繼承的對象模型

1)對象地址中按繼承順序分別放置派生類繼承下來的基類的虛函數表的地址和它的成員變量,派生類的成員變量放在最後

2)派生類的虛函數的地址在第一個虛函數表中

3)派生類的虛函數地址覆蓋基類虛函數地址(按以上單一繼承的條件)

 

重複繼承(鑽石繼承)

wKioL1bwKn3xsmGgAAAou43eTiQ407.png

重複繼承結構

 

運行下面這段程序:

#include<iostream>
using namespace std;
typedef void(*PFUN)();
class B1
{
public:
     B1() :_b1(1){}
     virtual void fun1()
     {
          cout << "B1::fun1()" << endl;
     }
      virtual void funB()
     {
          cout << "B1::funB()" << endl;
     }
private:
     int _b1;
};
class B2:public B1
{
public:
     B2() :_b2(2){}
     virtual void fun1()
     {
          cout << "B2::fun1()" << endl;
     }
     virtual void fun2()
     {
          cout << "B2::fun2()" << endl;
     }
     virtual void funB2()
     {
          cout << "B2::funB2()" << endl;
     }
private:
     int _b2;
};
class B3:public B1
{
public:
     B3() :_b3(3){}
     virtual void fun1()
     {
          cout << "B3::fun1()" << endl;
     }
     virtual void fun2()
     {
          cout << "B3::fun2()" << endl;
     }
      virtual void funB3()
     {
          cout << "B3::funB3()" << endl;
     }
private:
     int _b3;
};
class D:public B2,public B3
{
public:
     D() :_d(4){}
     void fun1()
     {
          cout << "D::fun1()" << endl;
     }
     virtual void fun2()
     {
          cout << "D::fun2()" << endl;
     }
      virtual void funD()
     {
          cout << "D::funD()" << endl;
     }
private:
     int _d;
};
void PrintVT(int b)
{
     PFUN pfun = NULL;
     int *ptr = (int *)b;
     int i = 0;
     while (ptr[i])
     {
          pfun = (PFUN)ptr[i];
          pfun();
          i++;
     }
}
int main()
{
     D d;
     PrintVT(*(int *)&d);
     cout << "---------" << endl;
     PrintVT(*((int *)&d+3));
     getchar();
     return 0;
}

我們在內存窗口可以看到:(vc++6.0)

 

wKioL1bwKdSCXcR5AAApXpsUaC0501.png

 

 程序運行結果:

wKiom1bwKiiwAfoEAAAR9hDoo0Q801.png

 根據內存分佈,重複繼承的內存佈局爲:

wKioL1bwL0ygsUd2AAAgU4uDNds640.png

重複繼承的對象模型

1)對象的地址按照直接基類繼承順序分別放置虛表地址和數據成員(繼承自基類的數據成員在前),然後放自己的數據成員。

2)虛函數表中先放置間接基類的虛函數地址,然後是直接基類的虛函數地址

3)派生類的虛函數放在第一個虛表中

4)基類中的虛函數被派生類中虛函數覆蓋

這種繼承方式存儲了兩份來自間接基類的數據成員,程序訪問該成員時導致二義性。因此我們通常用虛繼承。

 

虛繼承

 

wKiom1byubeQIo03AAAlfjnqCzA357.png

虛繼承結構

運行下面這段程序,我們可以在內存中看到對象d在內存中的存儲結構:

#include<iostream>
using namespace std;
typedef void(*PFUN)();
class B
{
public:
     B() :_b(1){}
     virtual void fun1()
     {
          cout << "B::fun1()" << endl;
     }
      virtual void funB()
     {
          cout << "B::funB()" << endl;
     }
private:
     int _b;
};
class B1:virtual public B
{
public:
     B1() :_b1(2){}
     virtual void fun1()
     {
          cout << "B1::fun1()" << endl;
     }
     virtual void fun2()
     {
          cout << "B1::fun2()" << endl;
     }
     virtual void funB1()
     {
          cout << "B1::funB1()" << endl;
     }
private:
     int _b1;
};
class B2:virtual public B
{
public:
     B2() :_b2(3){}
     virtual void fun1()
     {
          cout << "B2::fun1()" << endl;
     }
     virtual void fun2()
     {
          cout << "B2::fun2()" << endl;
     }
      virtual void funB2()
     {
          cout << "B2::funB2()" << endl;
     }
private:
     int _b2;
};
class D:public B1,public B2
{
public:
     D() :_d(4){}
     void fun1()
     {
          cout << "D::fun1()" << endl;
     }
     virtual void fun2()
     {
          cout << "D::fun2()" << endl;
     }
      virtual void funD()
     {
          cout << "D::funD()" << endl;
     }
private:
     int _d;
};
void PrintVT(int b)
{
     PFUN pfun = NULL;
     int *ptr = (int *)b;
     int i = 0;
     while (ptr[i])
     {
      pfun = (PFUN)ptr[i];
      pfun();
      i++;
 }
}
int main()
{
     D d;
     PrintVT(*(int *)&d);
     cout << "---------" << endl;
     PrintVT(*((int *)&d+3));
    cout << "---------" << endl;
     PrintVT(*((int *)&d+7));
     getchar();
     return 0;
}

我們在內存窗口可以看到:(vc++6.0)

wKiom1byvHaQXp7iAABfCLiOpzA599.png

程序運行結果:

wKioL1byvlDAJbb0AAATCoKvTT8359.png

根據內存分佈,虛繼承的內存佈局爲:

wKiom1byxtDCLI5dAAArGgaGbK8294.png

虛繼承的對象模型

1)虛繼承的對象地址處首先放置的是該類直接的第一個基類的虛函數表的地址,然後存儲一個地址,地址中存儲能夠找到虛基類的虛表地址的偏移量(其中-4可能是一個標誌,以0結束)然後存儲繼承自該類的數據成員

2)派生類的虛函數保存在第一個直接基類的虛函數表中

3)然後存儲第二個繼承自直接基類的虛函數表地址和偏移量地址及數據成員

4)最後存儲派生類自己的數據成員,以0結束

5)存儲虛基類的虛函數表的地址,虛基類的虛函數表地址可有前面保存的偏移量找到。

6)然後存儲繼承自虛基類的數據成員,我們可以看出虛繼承這種存儲模型只存儲一份來自虛基類的數據成員,因此避免了二義性。

 

 

 

 

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