一、多重繼承(無虛函數覆蓋)
?
下面,再讓我們來看看多重繼承中的情況,假設有下面這樣一個類的繼承關系。注意:子類并沒有覆蓋父類的函數。
?
class Base1
{
public: virtual void f() { cout << "Base1::f" << endl; } //虛函數定義virtual void g() { cout << "Base1::g" << endl; }virtual void h() { cout << "Base1::h" << endl; }
};class Base2
{
public: virtual void f() { cout << "Base2::f" << endl; } //虛函數定義virtual void g() { cout << "Base2::g" << endl; }virtual void h() { cout << "Base2::h" << endl; }
};class Base3
{
public:virtual void f() { cout << "Base3::f" << endl; }virtual void g() { cout << "Base3::g" << endl; }virtual void h() { cout << "Base3::h" << endl; }
};class Derive :public Base1, public Base2, public Base3 //多繼承的情況——無虛繼承覆蓋
{
public:virtual void f1() { cout << "Derive::f1" << endl; } //虛函數定義virtual void g1() { cout << "Derive::g1" << endl; }
};
對于子類實例中的虛函數表,是下面這個樣子:
?
我們可以看到:
- 每個父類都有自己的虛表。
- 子類的成員函數被放到了第一個父類的表中。(所謂的第一個父類是按照聲明順序來判斷的)
這樣做就是為了解決不同的父類類型的指針指向同一個子類實例,而能夠調用到實際的函數。
?
二、多重繼承(有虛函數覆蓋)
下面我們再來看看,如果發生虛函數覆蓋的情況。
class Base1
{
public: virtual void f() { cout << "Base1::f" << endl; }virtual void g() { cout << "Base1::g" << endl; }virtual void h() { cout << "Base1::h" << endl; }
};class Base2
{
public: virtual void f() { cout << "Base2::f" << endl; }virtual void g() { cout << "Base2::g" << endl; }virtual void h() { cout << "Base2::h" << endl; }
};class Base3
{
public: virtual void f() { cout << "Base3::f" << endl; }virtual void g() { cout << "Base3::g" << endl; }virtual void h() { cout << "Base3::h" << endl; }
};class Derive : public Base1, public Base2, public Base3
{
public:virtual void f() { cout << "Derive::f" << endl; } //唯一一個覆蓋的子類函數virtual void g1() { cout << "Derive::g1" << endl; }
};
?
下圖中,我們在子類中覆蓋了父類的f()函數。
下面是對于子類實例中的虛函數表的圖:
我們可以看見,三個父類虛函數表中的f()的位置被替換成了子類的函數指針。這樣,我們就可以任一靜態類型的父類來指向子類,并調用子類的f()了。如:
1 Derive d;2 Base1 *b1 = &d;3 Base2 *b2 = &d;4 Base3 *b3 = &d;5 b1->f(); //Derive::f()6 b2->f(); //Derive::f()7 b3->f(); //Derive::f()8 9 b1->g(); //Base1::g()
10 b2->g(); //Base2::g()
11 b3->g(); //Base3::g()
每次寫C++的文章,總免不了要批判一下C++。這篇文章也不例外。通過上面的講述,相信我們對虛函數表有一個比較細致的了解了。水可載舟,亦可覆舟。下面,讓我們來看看我們可以用虛函數表來干點什么壞事吧。
1. 通過父類型的指針訪問子類自己的虛函數
我們知道,子類沒有重載父類的虛函數是一件毫無意義的事情。因為多態也是要基于函數重載的。雖然在上面的圖中我們可以看到Base1的虛表中有Derive的虛函數,但我們根本不可能使用下面的語句來調用子類的自有虛函數:
Base1 *b1 = new Derive();
b1->g1(); //編譯出錯
任何妄圖使用父類指針想調用子類中的未覆蓋父類的成員函數的行為都會被編譯器視為非法,所以,這樣的程序根本無法編譯通過。但在運行時,我們可以通過指針的方式訪問虛函數表來達到違反C++語義的行為。(關于這方面的嘗試,通過閱讀后面附錄的代碼,相信你可以做到這一點)
?
2. 訪問non-public的虛函數
另外,如果父類的虛函數是private或是protected的,但這些非public的虛函數同樣會存在于虛函數表中,所以,我們同樣可以使用訪問虛函數表的方式來訪問這些non-public的虛函數,這是很容易做到的。如
#include<iostream>
using namespace std;class Base {
private:virtual void f() { cout << "Base::f" << endl; }
};class Derive : public Base {};typedef void(*Fun)(void);int main()
{Derive d;Fun pFun = (Fun)*((int*)*(int*)(&d) + 0);pFun();
}
?輸出結果:
?
?
三、多重繼承
下面,再讓我們來看看多重繼承中的情況,假設有下面這樣一個類的繼承關系。
注意:子類只overwrite了父類的f()函數,而還有一個是自己的函數(我們這樣做的目的是為了用g1()作為一個標記來標明子類的虛函數表)。而且每個類中都有一個自己的成員變量:
們的類繼承的源代碼如下所示:父類的成員初始為10,20,30,子類的為100
#include<iostream>
using namespace std;class Base1 {
public:int ibase1;Base1() :ibase1(10) {}virtual void f() { cout << "Base1::f()" << endl; }virtual void g() { cout << "Base1::g()" << endl; }virtual void h() { cout << "Base1::h()" << endl; }};class Base2 {
public:int ibase2;Base2() :ibase2(20) {}virtual void f() { cout << "Base2::f()" << endl; }virtual void g() { cout << "Base2::g()" << endl; }virtual void h() { cout << "Base2::h()" << endl; }
};class Base3 {
public:int ibase3;Base3() :ibase3(30) {}virtual void f() { cout << "Base3::f()" << endl; }virtual void g() { cout << "Base3::g()" << endl; }virtual void h() { cout << "Base3::h()" << endl; }
};class Derive : public Base1, public Base2, public Base3 {
public:int iderive;Derive() :iderive(100) {}virtual void f() { cout << "Derive::f()" << endl; }virtual void g1() { cout << "Derive::g1()" << endl; }
};int main()
{typedef void(*Fun)(void);Derive d;int** pVtab = (int**)&d;cout << "[0] Base1::_vptr->" << endl;Fun pFun = (Fun)pVtab[0][0];cout << " [0] ";pFun();pFun = (Fun)pVtab[0][1];cout << " [1] "; pFun();pFun = (Fun)pVtab[0][2];cout << " [2] "; pFun();pFun = (Fun)pVtab[0][3];cout << " [3] "; pFun();pFun = (Fun)pVtab[0][4];cout << " [4] "; cout << pFun << endl;cout << "[1] Base1.ibase1 = " << (int)pVtab[1] << endl;int s = sizeof(Base1) / 4;cout << "[" << s << "] Base2::_vptr->" << endl;pFun = (Fun)pVtab[s][0];cout << " [0] "; pFun();pFun = (Fun)pVtab[s][1];cout << " [1] "; pFun();pFun = (Fun)pVtab[s][2];cout << " [2] "; pFun();pFun = (Fun)pVtab[s][3];cout << " [3] ";cout << pFun << endl;cout << "[" << s + 1 << "] Base2.ibase2 = " << (int)pVtab[s + 1] << endl;s = s + sizeof(Base2) / 4;cout << "[" << s << "] Base3::_vptr->" << endl;pFun = (Fun)pVtab[s][0];cout << " [0] "; pFun();pFun = (Fun)pVtab[s][1];cout << " [1] "; pFun();pFun = (Fun)pVtab[s][2];cout << " [2] "; pFun();pFun = (Fun)pVtab[s][3];cout << " [3] ";cout << pFun << endl;s++;cout << "[" << s << "] Base3.ibase3 = " << (int)pVtab[s] << endl;s++;cout << "[" << s << "] Derive.iderive = " << (int)pVtab[s] << endl;
}
輸出結果:
使用圖片表示是下面這個樣子:
我們可以看到:
- 每個父類都有自己的虛表。
- 子類的成員函數被放到了第一個父類的表中。
- 內存布局中,其父類布局依次按聲明順序排列。
- 每個父類的虛表中的f()函數都被overwrite成了子類的f()。這樣做就是為了解決不同的父類類型的指針指向同一個子類實例,而能夠調用到實際的函數。
?
四、重復繼承
面我們再來看看,發生重復繼承的情況。所謂重復繼承,也就是某個基類被間接地重復繼承了多次。
下圖是一個繼承圖,我們重載了父類的f()函數。
其類繼承的源代碼如下所示。其中,每個類都有兩個變量,一個是整形(4字節),一個是字符(1字節),而且還有自己的虛函數,自己overwrite父類的虛函數。如子類D中,f()覆蓋了超類的函數, f1() 和f2() 覆蓋了其父類的虛函數,Df()為自己的虛函數。
#include<iostream>
using namespace std;class B
{
public:int ib;char cb;
public:B() :ib(0), cb('B') {}virtual void f() { cout << "B::f()" << endl; }virtual void Bf() { cout << "B::Bf()" << endl; }
};
class B1 : public B
{
public:int ib1;char cb1;
public:B1() :ib1(11), cb1('1') {}virtual void f() { cout << "B1::f()" << endl; }virtual void f1() { cout << "B1::f1()" << endl; }virtual void Bf1() { cout << "B1::Bf1()" << endl; }};
class B2 : public B
{
public:int ib2;char cb2;
public:B2() :ib2(12), cb2('2') {}virtual void f() { cout << "B2::f()" << endl; }virtual void f2() { cout << "B2::f2()" << endl; }virtual void Bf2() { cout << "B2::Bf2()" << endl; }};class D : public B1, public B2
{
public:int id;char cd;
public:D() :id(100), cd('D') {}virtual void f() { cout << "D::f()" << endl; }virtual void f1() { cout << "D::f1()" << endl; }virtual void f2() { cout << "D::f2()" << endl; }virtual void Df() { cout << "D::Df()" << endl; }};int main()
{typedef void(*Fun)(void);int** pVtab = NULL;Fun pFun = NULL;D d;pVtab = (int**)&d;cout << "[0] D::B1::_vptr->" << endl;pFun = (Fun)pVtab[0][0];cout << " [0] "; pFun();pFun = (Fun)pVtab[0][1];cout << " [1] "; pFun();pFun = (Fun)pVtab[0][2];cout << " [2] "; pFun();pFun = (Fun)pVtab[0][3];cout << " [3] "; pFun();pFun = (Fun)pVtab[0][4];cout << " [4] "; pFun();pFun = (Fun)pVtab[0][5];cout << " [5] 0x" << pFun << endl;cout << "[1] B::ib = " << (int)pVtab[1] << endl;cout << "[2] B::cb = " << (char)pVtab[2] << endl;cout << "[3] B1::ib1 = " << (int)pVtab[3] << endl;cout << "[4] B1::cb1 = " << (char)pVtab[4] << endl;cout << "[5] D::B2::_vptr->" << endl;pFun = (Fun)pVtab[5][0];cout << " [0] "; pFun();pFun = (Fun)pVtab[5][1];cout << " [1] "; pFun();pFun = (Fun)pVtab[5][2];cout << " [2] "; pFun();pFun = (Fun)pVtab[5][3];cout << " [3] "; pFun();pFun = (Fun)pVtab[5][4];cout << " [4] 0x" << pFun << endl;cout << "[6] B::ib = " << (int)pVtab[6] << endl;cout << "[7] B::cb = " << (char)pVtab[7] << endl;cout << "[8] B2::ib2 = " << (int)pVtab[8] << endl;cout << "[9] B2::cb2 = " << (char)pVtab[9] << endl;cout << "[10] D::id = " << (int)pVtab[10] << endl;cout << "[11] D::cd = " << (char)pVtab[11] << endl;
}
輸出結果:
下面是對于子類實例中的虛函數表的圖:(第一份圖為原作者的圖,第二幅圖為修改的圖)
?
我們可以看見,最頂端的父類B其成員變量存在于B1和B2中,并被D給繼承下去了。而在D中,其有B1和B2的實例,于是B的成員在D的實例中存在兩份,一份是B1繼承而來的,另一份是B2繼承而來的。所以,如果我們使用以下語句,則會產生二義性編譯錯誤:
1 2 3 4 |
|
注意,上面例程中的最后兩條語句存取的是兩個變量。雖然我們消除了二義性的編譯錯誤,但B類在D中還是有兩個實例,這種繼承造成了數據的重復,我們叫這種繼承為重復繼承。重復的基類數據成員可能并不是我們想要的。所以,C++引入了虛基類的概念。