c++中多态的實作
我們都知道,c++中的多态是在虛函數的基礎上實作的,用指向派生類的基類指針調用派生類(或基類)中自己的成員函數。那麼,具體是怎麼實作的呢?
其實它是通過虛函數表來實作的,虛函數表是儲存虛函數位址的一張表,若一個類中有虛函數,當程式運作時,編譯器通過在虛函數表中查找相應的虛函數的位址來調用該函數。
對象的繼承有如下幾類:
1.單一繼承
2.多重繼承
3.重複繼承(鑽石繼承)
4.虛繼承
下面我們分别來看一下各種繼承的記憶體布局:
單一繼承

單一繼承的結構
運作下面這段程式:
#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)
我們可以在記憶體中看到對象d在記憶體中的存儲結構如上圖所示,根據對象在記憶體中的的存儲結構我們可以取出記憶體中虛函數表的位址,進而拿到虛函數表中的虛函數位址,通過函數指針可以調用各個虛函數。這樣我們就可以知道虛函數表中所存是哪些虛函數的位址。
上面程式的功能是調用虛函數表中函數的位址所對應的函數,程式運作結果如下:
單一繼承的記憶體布局是:
單一繼承對象模型
1)對象位址的最前面放的是虛函數表的位址,然後根據繼承的順序依次放置成員變量的位址。
2)虛表裡按繼承順序先放置派生類從基類繼承來的虛函數的位址,然後是派生類自己的虛函數的位址。
3)滿足覆寫條件的派生類的虛函數位址覆寫了基類虛函數的位址。
覆寫:
1)不同作用域(基類和派生類中)
2)函數名相同,參數清單相同,傳回值類型相同(協變除外)
3)為虛函數(virtual)
注:協變是指基類虛函數傳回值為基類類型的指針,派生類虛函數的傳回值為派生類類型的指針
多重繼承
多重繼承結構
#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)
程式運作結果如下:
上面是第一個虛表中虛函數的調用,下面是第二個。
根據記憶體分布,多重繼承的記憶體布局為:
多重繼承的對象模型
1)對象位址中按繼承順序分别放置派生類繼承下來的基類的虛函數表的位址和它的成員變量,派生類的成員變量放在最後
2)派生類的虛函數的位址在第一個虛函數表中
3)派生類的虛函數位址覆寫基類虛函數位址(按以上單一繼承的條件)
重複繼承(鑽石繼承)
重複繼承結構
#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;
}
程式運作結果:
根據記憶體分布,重複繼承的記憶體布局為:
重複繼承的對象模型
1)對象的位址按照直接基類繼承順序分别放置虛表位址和資料成員(繼承自基類的資料成員在前),然後放自己的資料成員。
2)虛函數表中先放置間接基類的虛函數位址,然後是直接基類的虛函數位址
3)派生類的虛函數放在第一個虛表中
4)基類中的虛函數被派生類中虛函數覆寫
這種繼承方式存儲了兩份來自間接基類的資料成員,程式通路該成員時導緻二義性。是以我們通常用虛繼承。
虛繼承
虛繼承結構
運作下面這段程式,我們可以在記憶體中看到對象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;
}
程式運作結果:
根據記憶體分布,虛繼承的記憶體布局為:
虛繼承的對象模型
1)虛繼承的對象位址處首先放置的是該類直接的第一個基類的虛函數表的位址,然後存儲一個位址,位址中存儲能夠找到虛基類的虛表位址的偏移量(其中-4可能是一個标志,以0結束)然後存儲繼承自該類的資料成員
2)派生類的虛函數儲存在第一個直接基類的虛函數表中
3)然後存儲第二個繼承自直接基類的虛函數表位址和偏移量位址及資料成員
4)最後存儲派生類自己的資料成員,以0結束
5)存儲虛基類的虛函數表的位址,虛基類的虛函數表位址可有前面儲存的偏移量找到。
6)然後存儲繼承自虛基類的資料成員,我們可以看出虛繼承這種存儲模型隻存儲一份來自虛基類的資料成員,是以避免了二義性。