包含虛函數的類中隱含一個指針,叫做vptr(virtual table pointer,虛函數表指針)。 vptr 指向一個vtbl(virtual table,虛函數表)函數指針數組,該數組記錄實際調用的函數地址。對於多繼承的時候,情況稍微複雜一些。如果多個父類都包含虛函數,則類中vptr指針也會有多個。當子類對象實例賦值給父類時會對父類中的vptr進行初始化,使其指向含有實際訪問的vtbl函數指針數組。
#include <iostream>
using namespace std;
class empty
{
};
class base
{
public:
virtual void f(){cout<<"base, virtual function f()"<<endl;}
};
class derived: public base
{
public:
virtual void f1(){cout<<"derived, virtual function f1()"<<endl;}
};
int main(void)
{
base b;
base *pb;
derived *pd;
void (* pf)(void);
pd = new derived();
cout<<"size of empty class: "<<sizeof(empty)<<endl;
cout<<"size of base class: "<<sizeof(base)<<endl;
cout<<"size of derived class: "<<sizeof(derived)<<endl;
cout<<"base vptr address: "<<(int *)(&b)<<endl;
cout<<"function address in base: "<<(int *)*(int *)(&b)<<endl;
pf = (void (*)()) * ((int *)*(int *)(&b));
(*pf)();
pf();
return 0;
}
yongmi@yongmi-hn:~/c$ g++ virtual_class.cpp
yongmi@yongmi-hn:~/c$ ./a.out
size of empty class: 1
size of base class: 4
size of derived class: 4
base vptr address: 0xbfc30f50
function address in base: 0x8048c18
base, virtual function f()
base, virtual function f()
空類的大小爲1(每個類實例在內存中都有一個獨一無二的地址,爲了達到這個目的,編譯器往往會給一個空類隱含的加一個字節,這樣空類在實例化後在內存得到了獨一無二的地址),包含虛函數的類大小爲4(隱含一個vptr指針)。
關係如下所示:
vptr---->base:f
(int *)(&b)是vptr的地址,存放的內容爲*(int *)(&b),即vtbl數組地址。
(int *)*(int *)(&b),將vptr地址中的內容轉換爲int型指針,即vtbl數組中保存的值轉換爲int型指針。
(void (*)()) * ((int *)*(int *)(&b)),vtbl數組中保存的指針值轉換爲函數指針。
上面這個實例中可以看出,通過獲取vptr可以通過函數指針來訪問類中的成員函數(即使該成員函數爲私有也能訪問),這一點有違類的訪問控制權限。
C++父類的析構函數應該使用虛函數,否則可能會造成內存泄漏。看下面這個例子:
#include <iostream>
using namespace std;
class base
{
public:
~base ()
{
cout << "base, ~base()" << endl;
}
};
class derived:public base
{
private:
char *name;
public:
derived ()
{
name = new char[10];
}
virtual ~derived ()
{
delete []name;
cout << "derived, ~derived()" << endl;
}
};
int main (void)
{
derived d;
base *pb = new derived ();
delete pb;
}
yongmi@yongmi-hn:~/c$ g++ virtual_constructor.cpp
yongmi@yongmi-hn:~/c$ ./a.out
base, ~base()
derived, ~derived()
base, ~base()
運行結果顯示,類derived的析構函數只調用了一次,造成子類中申請的內存泄漏。將程序修改之後如下:
#include <iostream>
using namespace std;
class base
{
public:
virtual ~base ()
{
cout << "base, ~base()" << endl;
}
};
class derived:public base
{
private:
char *name;
public:
derived ()
{
name = new char[10];
}
virtual ~derived ()
{
delete []name;
cout << "derived, ~derived()" << endl;
}
};
int main (void)
{
derived d;
base *pb = new derived ();
delete pb;
}
yongmi@yongmi-hn:~/c$ g++ virtual_constructor.cpp
yongmi@yongmi-hn:~/c$ ./a.out
derived, ~derived()
base, ~base()
derived, ~derived()
base, ~base()
此時程序調用了類derived兩次析構函數,沒有造成內存泄漏。
原因是因爲將子類對象賦給父類時,如果子類對象動態申請了內存空間,而父類析構函數不是虛函數時,子類的析構函數不能調用,所以無法釋放內存空間。
參考資料: