探索C 物件模型

2021-08-28 17:28:54 字數 3009 閱讀 3234

前兩篇部落格主要了解了多型和繼承的基礎,可是當我們在學習多型和繼承的時候,難免會碰到很多關於c++物件模型的問題,例如菱形繼承中的資料冗餘如何解決,虛基表是如何解決菱形繼承中資料冗餘問題等,這一篇部落格我們以c++中多型與繼承為基礎,探索c++物件模型。

首先我們先來看看虛函式,虛函式就是在函式名前面加virtual的函式,虛函式可以解決繼承的很多問題,而對於多型而言,沒有虛函式就沒有多型,虛函式存在虛函式表裡供物件呼叫實現多型。

純虛函式就是在成員函式的形參後面加上=0,成員函式被宣告為純虛函式

包含純虛函式的類被叫做**抽象類(介面類)**抽象類不能例項化出物件,只有在派生類中重新定義後,派生類才能例項化出物件。

我們用**看看純虛函式

class person

; class student : public person{};

抽象類是強制重寫的一種機制

接下來我們來看看虛函式與虛函式表,虛函式是c++實現多型的方式,c++用在父類中實現虛函式,子類完成對虛函式的重寫從而完成了型別的靈活呼叫。

下面是乙個含有虛函式的類

我們可以看出當我們將乙個類中的成員函式宣告為虛函式後,會在編譯的時候生成一張虛函式表,在記憶體的表現形式是在a中有乙個vfptr指標,這個指標指向虛函式表,而虛函式表中存著這個類中的虛函式。

我們來總結下虛函式的使用規則

派生類重寫基類的虛函式實現多型,要求函式名、引數列表、返回值完全相同。(協變除外)

基類中定義了虛函式,在派生類中該函式始終保持虛函式的特性。

只有類的成員函式才能定義為虛函式。

靜態成員函式不能定義為虛函式。

如果在類外定義虛函式,只能在宣告函式時加virtual,類外定義函式時不能加virtual。

不要在建構函式和析構函式裡面呼叫虛函式,在建構函式和析構函式中,物件是不完整的,可能會發生未定義的行為。

最好把基類的析構函式宣告為虛函式。(why?另外析構函式比較特殊,因為派生類的析構函式跟基類的析構函式名稱不一樣,但是構成覆蓋,這裡是因為編譯器做了特殊處理)

建構函式不能為虛函式,雖然可以將operator=定義為虛函式,但是最好不要將operator=定義為虛函式,因為容易使用時容易引起混淆.

在看完虛函式後,我們了解虛函式可以在c++中完成重寫,然而我們經常將重寫,過載,重定義互相混淆,這裡做出區分

接下來我們來看看單繼承中的物件模型,在單繼承中虛函式表在子類中是如何完成多型呼叫的

我們先來一段**看看單繼承中的物件模型

class a

virtual void fun2()

protected:

int num;

};class c :public a

virtual void fun3()

virtual void fun4()

};

這裡我們可以**下,a中實現了虛函式fun2與fun1,c中重寫了a的fun1,自己實現了虛函式fun3,fun4我們呼叫監視視窗看看

可是我們發現在c的物件模型中虛表裡面只有兩個虛函式,這裡其實是編譯器的問題

那麼我們嘗試自己實現乙個列印虛表的函式

void printvtable(int* vtable)

cout << endl;

這裡實現這個函式的原理是訪問陣列的方式去訪問函式指標,取出虛表首位址後解引用進入虛表列印出每乙個虛函式的位址

printvtable((int*)(*(int*)(&c)));
這個傳參很值得解釋下

首先我們拿到b的首位址,將他強轉成int型別,讓他讀取到虛表的位址再然後對那個位址解引用,我們已經拿到虛表的首位址的內容(虛表裡面儲存的第乙個函式的位址)了,但是此時這個變數的型別解引用後是int,不能夠傳入函式,所以我們再對他進行乙個int的強制型別轉換,這樣我們就傳入引數了,開始函式執行了,我們一切都是在可控的情況下使用強轉,使用強轉你必須要特別清楚的知道記憶體的分布結構。

接下來我們剖析下多繼承的物件模型,這個就沒有單繼承這個簡單了

首先我們實現兩個父類,兩個父類中都有fun1與fun2,子類繼承兩個父類,看看子類的物件模型

#include#includeusing namespace std;

class a

virtual void fun2()

protected:

int num;

};class b

virtual void fun2()

protected:

int num;

};class c :public a,public b

virtual void fun3()

virtual void fun4() };

typedef void(*func)(void);

void printvtable(int* vtable)

cout << endl;

}int main()

這裡子類繼承了a與b兩張虛表,我們依舊是將虛表列印出來看看子類的物件模型

這裡我們看到c中的fun1完成了函式的重寫,虛表中繼承的是a的fun2,這裡很明顯是繼承了a的虛表,所以我們得出結論子類的虛表繼承先繼承的類的虛表

深度探索C 物件模型

傳世經典書叢 深度探索c 物件模型 美 stanley b.lippman 斯坦利 b.李普曼 著 侯捷 譯 isbn978 7 121 14952 8 2012年1月出版 定價 69.00元 16開 356頁 宣傳語 如果你是一位c 程式設計師,渴望對於底層知識獲得乙個完整的了解,那麼本書正適合你...

深度探索C 物件模型

傳世經典書叢 深度探索c 物件模型 美 stanley b.lippman 斯坦利 b.李普曼 著 侯捷譯 isbn978 7 121 14952 8 2012年1月出版 定價 69.00元 16開 356頁 宣傳語 如果你是一位c 程式設計師,渴望對於底層知識獲得乙個完整的了解,那麼本書正適合你 ...

C 物件記憶體模型探索

實驗環境 ubuntu 18.04 64 bit gcc 7.3.0 g 7.3.0,編譯使用 m32選項啟用32位環境 實驗步驟 1 不含有虛函式的基類,如下 include class base private int i int main 執行結果 kevin kvm study temp g...