虛函數及繼承

轉自:http://blog.chinaunix.net/uid-25132162-id-1564955.html

——————————————————我是分割線—————————————————————

1、空類,空類單繼承,空類多繼承的sizeof
  1. #include <iostream>
  2. using namespace std;

  3. class Base1
  4. {

  5. };

  6. class Base2
  7. {

  8. };

  9. class Derived1:public Base1
  10. {

  11. };

  12. class Derived2:public Base1,public Base2
  13. {

  14. };

  15. int main()
  16. {
  17.     Base1 b1;
  18.     Base2 b2;
  19.     Derived1 d1;
  20.     Derived2 d2;
  21.     cout<<"sizeof(Base1) = "<<sizeof(Base1)<<" sizeof(b1) = "<<sizeof(b1)<<endl;
  22.      cout<<"sizeof(Base2) = "<<sizeof(Base2)<<" sizeof(b2) = "<<sizeof(b2)<<endl;
  23.     cout<<"sizeof(Derived1) = "<<sizeof(Derived1)<<" sizeof(d1) = "<<sizeof(d1)<<endl;
  24.     cout<<"sizeof(Derived2) = "<<sizeof(Derived2)<<" sizeof(d1) = "<<sizeof(d1)<<endl;
  25.   
  26.     return 0;
  27. }
結果爲:
sizeof(Base1) = 1 sizeof(b1) = 1
sizeof(Base2) = 1 sizeof(b2) = 1
sizeof(Derived1) = 1 sizeof(d1) = 1
sizeof(Derived2) = 1 sizeof(d1) = 1
可以看出所有的結果都是1。
 
2、含有虛函數的類以及虛繼承類的sizeof
虛函數(Virtual Function)是通過一張虛函數表(Virtual Table)來實現的。編譯器必需要保證虛函數表的指針存在於對象實例中最前面的位置(這是爲了保證正確取到虛函數的偏移量)。

假設我們有這樣的一個類:

class Base {

public:

virtual void f() { cout << "Base::f" << endl; }

virtual void g() { cout << "Base::g" << endl; }

virtual void h() { cout << "Base::h" << endl; }

};

當我們定義一個這個類的實例,Base b時,其b中成員的存放如下:

指向虛函數表的指針在對象b的最前面。

虛函數表的最後多加了一個結點,這是虛函數表的結束結點,就像字符串的結束符“\0”一樣,其標誌了虛函數表的結束。這個結束標誌的值在不同的編譯器下是不同的。在WinXP+VS2003下,這個值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,這個值是如果1,表示還有下一個虛函數表,如果值是0,表示是最後一個虛函數表。

因爲對象b中多了一個指向虛函數表的指針,而指針的sizeof是4,因此含有虛函數的類或實例最後的sizeof是實際的數據成員的sizeof加4。

下面將討論針對基類含有虛函數的繼承討論

(1)在派生類中不對基類的虛函數進行覆蓋,同時派生類中還擁有自己的虛函數,比如有如下的派生類:

class Derived: public Base

 {

public:

virtual void f1() { cout << "Derived::f1" << endl; }

virtual void g1() { cout << "Derived::g1" << endl; }

virtual void h1() { cout << "Derived::h1" << endl; }

};

基類和派生類的關係如下:

當定義一個Derived的對象d後,其成員的存放如下:

可以發現:

       1)虛函數按照其聲明順序放於表中。

       2)父類的虛函數在子類的虛函數前面。

此時基類和派生類的sizeof都是數據成員的sizeof加4。

(2)在派生類中對基類的虛函數進行覆蓋,假設有如下的派生類:

class Derived: public Base

 {

public:

virtual void f() { cout << "Derived::f" << endl; }

virtual void g1() { cout << "Derived::g1" << endl; }

virtual void h1() { cout << "Derived::h1" << endl; }

};

基類和派生類之間的關係:其中基類的虛函數f在派生類中被覆蓋了

當我們定義一個派生類對象d後,其d的成員存放爲:

可以發現:

1)覆蓋的f()函數被放到了虛表中原來父類虛函數的位置。

2)沒有被覆蓋的函數依舊。

這樣,我們就可以看到對於下面這樣的程序,

Base *b = new Derive();

b->f();

由b所指的內存中的虛函數表的f()的位置已經被Derive::f()函數地址所取代,於是在實際調用發生時,是Derive::f()被調用了。這就實現了多態。

(3)多繼承:無虛函數覆蓋

假設基類和派生類之間有如下關係:

對於子類實例中的虛函數表,是下面這個樣子:

我們可以看到:

1) 每個父類都有自己的虛表。

2) 子類的成員函數被放到了第一個父類的表中。(所謂的第一個父類是按照聲明順序來判斷的)

由於每個基類都需要一個指針來指向其虛函數表,因此d的sizeof等於d的數據成員加3*4=12。

(4)多重繼承,含虛函數覆蓋

假設,基類和派生類又如下關係:派生類中覆蓋了基類的虛函數f

下面是對於子類實例中的虛函數表的圖:

我們可以看見,三個父類虛函數表中的f()的位置被替換成了子類的函數指針。這樣,我們就可以任一靜態類型的父類來指向子類,並調用子類的f()了。如:

Derive d;

Base1 *b1 = &d;

Base2 *b2 = &d;

Base3 *b3 = &d;

b1->f(); //Derive::f()

b2->f(); //Derive::f()

b3->f(); //Derive::f()

b1->g(); //Base1::g()

b2->g(); //Base2::g()

b3->g(); //Base3::g()

3、一個關於含虛函數及虛繼承的sizeof計算

  1. #include <iostream>
  2. using namespace std;

  3. class Base
  4. {
  5. public:
  6.     virtual void f();
  7.     virtual void g();
  8.     virtual void h();
  9. };

  10. class Derived1:public Base
  11. {
  12. public:
  13.     virtual void f1();
  14.     virtual void g1();
  15.     virtual void h1();
  16. };

  17. class Derived2:public Base
  18. {
  19. public:
  20.     virtual void f();
  21.     virtual void g1();
  22.     virtual void h1();
  23. };

  24. class Derived3:virtualpublic Base
  25. {
  26. public:
  27.     virtual void f1();
  28.     virtual void g1();
  29.     virtual void h1();
  30. };

  31. class Derived4:virtualpublic Base
  32. {
  33. public:
  34.     virtual void f();
  35.     virtual void g1();
  36.     virtual void h1();
  37. };

  38. class Derived5:virtualpublic Base
  39. {
  40. public:
  41.     virtual void f();
  42.     virtual void g();
  43.     virtual void h();
  44. };

  45. class Derived6:virtualpublic Base
  46. {

  47. };

  48. int main()
  49. {
  50.     cout<<sizeof(Base)<<endl;//4
  51.     cout<<sizeof(Derived1)<<endl;//4
  52.     cout<<sizeof(Derived2)<<endl;//4
  53.     cout<<sizeof(Derived3)<<endl;//12
  54.     cout<<sizeof(Derived4)<<endl;//12
  55.     cout<<sizeof(Derived5)<<endl;//8
  56.     cout<<sizeof(Derived6)<<endl;//8

  57.     return 0;
  58. }
對於Base, Derived1和Derived2的結果根據前面關於繼承的分析是比較好理解的,不過對於虛繼承的方式則有點不一樣了,根據結果自己得出的一種關於虛繼承的分析,如對Derived3或Derived4定義一個對象d,其裏面會出現三個跟虛函數以及虛繼承的指針,因爲是虛繼承,因此引入一個指針指向虛繼承的基類,第二由於在基類中有虛函數,因此需要指針指向其虛函數表,由於派生類自己本身也有自己的虛函數,因爲採取的是虛繼承,因此它自己的虛函數不會放到基類的虛函數表的後面,而是另外分配一個只存放自己的虛函數的虛函數表,於是又引入一個指針,從例子中看到Derived5和Derived6的結果是8,原因是在派生類要麼沒有自己的虛函數,要麼全部都是對基類虛函數的覆蓋,因此就少了指向其派生類自己的虛函數表的指針,故結果要少4。(這個是個人的分析,但原理不知道是不是這樣的)
發佈了209 篇原創文章 · 獲贊 76 · 訪問量 36萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章