深入理解c 多型實現原理

2021-08-20 03:20:48 字數 1573 閱讀 8107

多型是指通過基類的指標或者引用,在執行時動態呼叫實際繫結物件函式的行為。與之相對應的編譯時繫結函式稱為靜態繫結。多型是物件導向程式設計的核心思想之一,因此我們有必要深入探索一下它的實現原理。理解了原理才能更好的使用。

前置條件

現有**如下所示,非常簡單的例子。通過基類的引用呼叫recv函式來觸發多型。接下來的分析涉及彙編知識,如果還沒熟悉彙編,可以看另外一篇文章深入理解c++函式呼叫的引數傳遞與區域性變數申

#include #include #define trace(fmt, ...) printf("[trace] %s:%s:%d " fmt, __file__, __function__, __line__, ##__va_args__)

class iclient

; virtual ~iclient(){};

virtual ssize_t recv(char *buff, size_t len) = 0;

};class cstreamclient: public iclient

; ~cstreamclient(){};

ssize_t recv(char *buff, size_t len)

};int main(int argc, char **argv)

分析

我們都知道擁有虛函式的類都有屬於自己的虛表存放在.text段中,例項化後物件擁有乙個內建變數_vptr虛表指標,它指向了實際物件的虛表。那麼這個_vptr在**初始化呢?當然是建構函式。如下圖所示,cstreamclient與iclient都會在自己的建構函式中初始化_vptr為自己的虛表位址。不過父類構造先於子類建構函式執行,因此cstreamclient物件指向的是自己的虛表。

接下來我們對main反彙編**分析如下所示,client物件處於棧中。首先計算出client物件的位址。接下來解引用得到_vptr的值。值得注意的是_vptr並非指向虛表起始位址,而是+0x08。起始4位元組猜測是保留,畢竟全是0x00;接下來4位元組存放的是物件的型別資訊,反彙編typeid()函式你就會發現它就是在讀取這個位址指向的typeinfo。

得到_vptr位址後+0x08的偏移得到虛表存放recv位址的位址,再解引用就得到實際繫結物件的recv函式位址了。接下來是各個引數入棧,再跳轉執行。

那麼,這就結束了嗎?眼尖的你肯定發現了,為什麼會有兩個的析構函式?函式宣告還是乙個模子出來的。那麼我們對這兩個析構反彙編分析對比一下有什麼不同。有注意到什麼不同嗎?

左側的析構函式賦值eax為零,eax分為[16位ax|8位ah|8位al]。那麼test指令將會置位zf標誌位,因此接下來的je指令將會跳轉,也就是不進行free的操作。右側的析構函式在呼叫左側析構的基礎上進行了釋放記憶體。那麼就是說它們分別是為棧上和堆上的物件準備的。

深入理解多型

能將每個函式都申明為虛函式,但是會影響效率,不建議這樣做,虛函式指標呼叫重寫函式是在程式執行時候進行的,因此需要一些定址操作才能真正呼叫函式,如果都設定成虛函式,效率會低很多 多型的實現效果 呼叫同樣的語句能表現不同的表現形式 多型實現的三個條件 有繼承,有虛函式重寫,有父類指標指向子類物件 多型的...

深入理解多型

1 黎克特制替換 概述 在子類物件中可以替換所有使用的父類物件 原則 父類物件是不能替換子類物件的 應用 使繼承復用成為可能,當子類可以替換父類時,軟體功能不受影響,父類才能真正被復用,而子類也能夠在父類的基礎上增加新的行為 關鍵字 is和a操作符 2 is和as操作符的使用 is操作符用於檢查物件...

深入理解多型

我們知道子類物件可以賦給父類物件,也可以說子類物件可以完全替換父類物件並出現在父類物件可以出現的任何地方,且程式的行為不會發生改變,但反過來父類物件是不能替換子類物件的!比如汽車類物件就不能替換卡車類物件 雖然卡車也是汽車 因為汽車包含的範圍比卡車要大,它也可以是私家車!這種特性就被稱為 黎克特制替...