c++虛擬函式表與虛解構函式
由於本人才疏學淺,本文難免存在遺漏之處,歡迎大家留言指正,本人將感激不盡。
C++虛擬函式表與虛解構函式
1.靜態聯編和動態聯編
聯編:將原始碼中的函式呼叫解釋為要執行函式程式碼。
靜態聯編:編譯時能確定唯一函式。
在C中,每個函式名都能確定唯一的函式程式碼。
在C++中,因為有函式過載,編譯器須根據函式名,引數才能確定唯一的函式程式碼。
動態聯編:編譯時不能確定呼叫的函式程式碼,執行時才能。
C++中因為多型的存在,有時編譯器不知道使用者將選擇哪種型別的物件,因此無法確定呼叫的唯一性,只有在執行時才能確定。
2.虛成員函式,指標或引用,動態聯編
指標或引用才能展現類的多型性。
當類中的方法宣告為virtual時,使用指標或引用呼叫該方法,就是動態聯編。
若是普通方法,則為靜態聯編。
示例如下:
class Test { public: virtual show() { std::cout<<"Test::show()"<<std::endl; } }; class SubTest:public Test { public: virtual show() { std::cout<<"SubTest::show()"<<std::endl; } }; int main() { SubTest subTest; Test * p = &subTest;//指向子類的指標 Test & a = subTest;//子類的引用 Test * p2 = new Test;//指向父類的指標 p->show(); a.show(); p2->show(); return 0; }
程式沒有釋放記憶體,我們將在後面解構函式的時候,完善該程式。
3.動態聯編使用原則
動態聯編,需要跟蹤基類指標或引用指向的實際物件型別,因此效率低於靜態聯編。
1)當類不會用作基類時,成員函式不要宣告為virtual
2)當成員函式不重新定義基類的方法,成員函式不要宣告為virtual
4.關於虛擬函式
1)父類成員函式若宣告為virtual,則子類中也是虛的,若要重新定義該方法,可顯式加上virtual關鍵字,也可不加,編譯器編譯時會自動加上。
2)使用指向物件的引用或指標來呼叫虛方法,將使用具體物件型別定義的方法,而不一定是引用或指標型別定義的方法。
SubTest subTest;
Test * p = &subTest;//指向子類的指標
p->show();//將呼叫SubTest物件定義的show()方法
5.虛擬函式的工作原理
當類中存在虛擬函式時,編譯器預設會給物件新增一個隱藏成員。該成員為一個指向虛擬函式表(virtual function table,vtbl)的指標。
虛擬函式表是一個儲存了虛擬函式地址的陣列。編譯器會檢查類中所有的虛擬函式,依次將每個虛擬函式的地址,存入虛擬函式表。
class Test
{
public:
virtual show()
{
std::cout<<"Test::show()"<<std::endl;
}
private:
int a;
};
class SubTest:public Test
{
public:
virtual show()
{
std::cout<<"SubTest::show()"<<std::endl;
}
};
記憶體結構圖如下所示:
可以看出,父類和子類有獨立的虛擬函式表,且虛擬函式表中虛擬函式指標也指向各自的虛擬函式地址,
若子類沒有覆蓋父類中的show方法,則虛擬函式指標show_ptr指向的虛擬函式show()的地址是一樣的,均指向父類show()函式地址。虛擬函式表的存在和動態聯編,就是多型的原理。
6.虛解構函式
1)建構函式是特殊的,是沒有虛擬函式的概念的。
建構函式是不繼承的,建立子類物件時,將呼叫子類的建構函式,子類的建構函式將自動呼叫父類的建構函式。
2)解構函式應是虛擬函式,除非類不用做基類。
我們看下面的程式碼:
Test *p = new SubTest;
delete p;
p=NULL;
由虛擬函式表,我們知道,若解構函式不宣告為virtual,則呼叫的將是Test類的解構函式,而沒有呼叫SubTest類的解構函式,此時造成了記憶體洩露。
所以解構函式必須宣告為虛擬函式,呼叫的將是子類SubTest的解構函式,
我們還需要知道的一點是,子類解構函式,一定會呼叫父類解構函式,釋放父類物件,則記憶體安全釋放