準備工作
1、vs2012使用命令列選項檢視物件的記憶體布局
微軟的visual studio提供給使用者顯示c++物件在記憶體中的布局的選項:/d1reportsingleclasslayout。使用方法很簡單,直接在[專案p]選項下找到「visual屬性」後點選即可。切換到cpp檔案所在目錄下輸入如下的命令即可
c1 [filename].cpp/d1reportsingleclasslayout[classname]
其中[filename].cpp就是我們想要檢視的class所在的cpp檔案,[classname]指我們想要檢視的class的類名。(下面舉例說明...)
虛繼承和虛函式是完全無相關的兩個概念。
虛繼承是解決c++多重繼承問題的一種手段,從不同途徑繼承來的同一基類,會在子類中存在多份拷貝。這將存在兩個問題:
其一,浪費儲存空間;
第二,存在二義性問題,通常可以將派生類物件的位址賦值給基類物件,實現的具體方式是,將基類指標指向繼承類(繼承類有基類的拷貝)中的基類物件的位址,但是多重繼承可能存在乙個基類的多份拷貝,這就出現了二義性。
虛繼承可以解決多種繼承前面提到的兩個問題:
虛繼承底層實現原理與編譯器相關,一般通過虛基類指標和虛基類表實現,每個虛繼承的子類都有乙個虛基類指標(占用乙個指標的儲存空間,4位元組)和虛基類表(不占用類物件的儲存空間)(需要強調的是,虛基類依舊會在子類裡面存在拷貝,只是僅僅最多存在乙份而已,並不是不在子類裡面了);當虛繼承的子類被當做父類繼承時,虛基類指標也會被繼承。
實際上,vbptr指的是虛基類表指標(virtual base table pointer),該指標指向了乙個虛基類表(virtual table),虛表中記錄了虛基類與本類的偏移位址;通過偏移位址,這樣就找到了虛基類成員,而虛繼承也不用像普通多繼承那樣維持著公共基類(虛基類)的兩份同樣的拷貝,節省了儲存空間。
在這裡我們可以對比虛函式的實現原理:他們有相似之處,都利用了虛指標(均占用類的儲存空間)和虛表(均不占用類的儲存空間)。
虛基類依舊存在繼承類中,只占用儲存空間;虛函式不占用儲存空間。
虛基類表儲存的是虛基類相對直接繼承類的偏移;而虛函式表儲存的是虛函式位址。
補充:1、d繼承了b,c也就繼承了兩個虛基類指標
2、虛基類表儲存的是,虛基類相對直接繼承類的偏移(d並非是虛基類的直接繼承類,b,c才是)
#if 0測試//測試虛表的存在
#include using namespace std;
class a
virtual void run()
virtual void run1()
virtual void run2()
};class b : public a
virtual void run1()
};class c :public a
virtual void run1()
virtual void run3()
};class d :/*virtual*/ public a
virtual void run1()
virtual void run2()
virtual void run3()
};int test()
//cout << (int)pvtable[1] << endl;
//cout << (int)pvtable[2] << endl;
getchar();
return 0;
}int main(void)
#endif
一、二:單個繼承的不同情況
#if 0測試三:多重繼承// 測試一:單個虛繼承,不帶虛函式
// 虛繼承與繼承的區別
// 1. 多了乙個虛基指標
// 2. 虛基類位於派生類儲存空間的最末尾
// 測試二:單個虛繼承,帶虛函式
// 1.如果派生類沒有自己的虛函式,此時派生類物件不會產生
// 虛函式指標
// 2.如果派生類擁有自己的虛函式,此時派生類物件就會產生自己本身的虛函式指標,
// 並且該虛函式指標位於派生類物件儲存空間的開始位置
//#pragma vtordisp(off)
#include using std::cout;
using std::endl;
class a
//virtual
void f()
private:
int _ia;
};class b
: virtual public a
void fb()
virtual void f()
#if 1
virtual void fb2()
#endif
private:
int _ib;
};int main(void)
#endif
// 測試三:多重繼承(帶虛函式)測試四:鑽石型繼承// 1. 每個基類都有自己的虛函式表
// 2. 派生類如果有自己的虛函式,會被加入到第乙個虛函式表之中
// 3. 記憶體布局中, 其基類的布局按照基類被宣告時的順序進行排列
// 4. 派生類會覆蓋基類的虛函式,只有第乙個虛函式表中存放的是
// 真實的被覆蓋的函式的位址;其它的虛函式表中存放的並不是真實的
// 對應的虛函式的位址,而只是一條跳轉指令
#if 1
#pragma vtordisp(off)
#include using std::cout;
using std::endl;
class base1
/*virtual*/ void f()
/*virtual*/ void g()
/*virtual*/ void h()
private:
int _ibase1;
};class base2
virtual void f()
/*virtual*/ void g()
/*virtual*/ void h()
private:
int _ibase2;
};class base3
virtual void f()
/*virtual*/ void g()
/*virtual*/ void h()
private:
int _ibase3;
};class derived
: virtual public base1
//, virtual public base2
//, public base3
void f()
/*virtual*/ void g1()
private:
int _iderived;
};int main(void)
#endif
// 測試四:鑽石型虛繼承(菱形繼承)道友可以自己將嘗試每種情況下程式記憶體分布的情況,以便更清晰的認識,虛函式與虛繼承。//虛基指標所指向的虛基表的內容:
// 1. 虛基指標的第一條內容表示的是該虛基指標距離所在的子物件的首位址的偏移
// 2. 虛基指標的第二條內容表示的是該虛基指標距離虛基類子物件的首位址的偏移
#if 0
#pragma vtordisp(off)
#include using std::cout;
using std::endl;
class b
virtual void f()
virtual void bf()
private:
int _ib;
char _cb;
};class b1 : virtual public b
virtual void f()
#if 1
virtual void f1()
virtual void bf1()
#endif
private:
int _ib1;
char _cb1;
};class b2 : virtual public b
virtual void f()
#if 1
virtual void f2()
virtual void bf2()
#endif
private:
int _ib2;
char _cb2;
};class d : public b1, public b2
virtual void f()
#if 1
virtual void f1()
virtual void f2()
virtual void df()
#endif
private:
int _id;
char _cd;
};int main(void)
#endif
C 之菱形繼承與虛繼承 含虛函式
物件導向的三大特徵 封裝,多型,繼承 前面我們已經講了繼承的一些知識點,在這基礎上,我們講的時候再涉獵一些多型的只是。下面我們先接著上次講有虛函式的菱形虛繼承 首先什麼是虛函式。虛函式 在類裡面,函式前面有virtual關鍵字的成員函式就是虛函式。塊 class base base virtual ...
虛函式 虛繼承 C
關於虛表,我們就要用到乙個關鍵字 virtual,可以修飾函式,也可以修飾類。類的成員函式被virtual修飾之後,就成為了虛函式 修飾類,主要是虛繼承。在此之前,我們首先要了解乙個概念 物件模型,也就是說,乙個基類形成之後,裡面的成員是怎麼存放的,當派生類繼承基類之後,派生類的成員是怎麼存放的。我...
虛函式,虛繼承與虛函式表
c 實現多型機制 模板技術,rtti 技術,虛函式技術,要麼是試圖做到在編譯時決議,要麼試圖做到執行時決議 虛函式 帶有 關鍵字的函式,並且不帶有 標誌的 虛繼承帶有 關鍵字的繼承,基類被稱為虛基類,會在自己物件的例項中產生虛基類指標 虛函式與菱形繼承的問題 當發生繼承時,如果派生類重寫了基類的虛函...