說到虛函數,首先要講一下OOP中的多態,多態簡單的說就是一個接口,多種實現.
多態分為編譯時多態和運行時多態。
編譯時多態主要體現在函數重載;
運行時多態是指程序在運行時動態地識別對象,實現不同的行為,這是通過虛函數+繼承實現的。
虛函數必須是類的非靜態成員函數,也不能是構造函數, 訪問權限為public
, 雖然在語法上設置成 private
和 protect
并沒有錯誤,但虛函數的目的是為了實現多態,所以設置成 protect
和 private
是沒有意義的。
靜態成員函數不能是 virtual 函數的原因
- 靜態成員函數可以不通過對象來調用,靜態成員函數沒有隱藏的this指針;virtual函數只能通過對象來調用,實現需要依靠隱藏的this指針
- 靜態成員函數是在編譯時就綁定了,而virtual函數在運行時才進行綁定
構造函數不能是 virtual 函數的原因
- 首先從設計理念上來說,構造函數不需要設置成 virtual 函數,從實現機制來說,構造函數也無法實現 virtual 函數
- 構造函數顧名思義就是構建一個對象,而虛函數是動態的識別對象的類型,這個對象屬于是基類、派生類還是更深層次的類,這是運行在對象已經存在的基礎上的。所以虛函數的調用必須在構造函數調用之后
- 虛函數的執行依賴于虛函數指針 vptr, 而 vptr 的初始化即讓 vptr 指向 虛函數表 V-table 是在構造函數中實現的,所以
構造函數不能是虛函數
虛函數表 vtable 和虛函數表指針 vptr
C++的虛函數的實現機制依靠的就是 vtable 和 vptr。
vtable 實際上就是 virtual 函數的地址表,這張表解決了繼承和覆蓋的問題
例如:
class A {
public:
virtual void f();
virtual void g();
};
class B: public A{
public:
virtual void f();
virtual void h();
};
B::f() | A::g() | B::h() | 0 |
---|
上面表格則為B的 vtable, 最后一個值如果為1說明還有下一個虛函數表,存在于多重繼承的情況下。
B類的內存分布的第一項即是 vptr,該指針指向B的 vtable
注意:
vtable 在編譯時期確立, vptr 在運行期確立。
在繼承中析構函數一定要被設置成虛函數
如果不設置成虛函數,那么當父類的指針調用子類的對象,該指針調用析構函數,將會調用父類的析構函數,而子類多出的那一部分數據內存將無法被釋放。