您好,登錄后才能下訂單哦!
類的虛函數表是一塊連續的內存,每個內存單元中記錄一個JMP指令的地址
注意的是,編譯器會為每個有虛函數的類創建一個虛函數表,該虛函數表將被該類的所有對象共享。類的每個虛成員占據虛函數表中的一行。如果類中有N個虛函數,那么其虛函數表將有N*4字節的大小。 虛函數(Virtual Function)是通過一張虛函數表(Virtual Table)來實現的。簡稱為V-Table。在這個表中,主要是一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其真實反應實際的函數。這樣,在有虛函數的類的實例中這個表被分配在了這個實例的內存中,所以,當用父類的指針來操作一個子類的時候,這張虛函數表就顯得由為重要了,它就像一個地圖一樣,指明了實際所應該調用的函數。 編譯器應該是保證虛函數表的指針存在于對象實例中最前面的位置(這是為了保證取到虛函數表的有最高的性能——如果有多層繼承或是多重繼承的情況下)。 這意味著可以通過對象實例的地址得到這張虛函數表,然后就可以遍歷其中函數指針,并調用相應的函數。
在方法定義時加上virtual,表示此方法是虛擬方法,可供子類覆蓋,修改父類的執行
構造函數不能用虛擬,因為用也沒用,不管是在棧上構造對象,還是在堆上構造對象,也不管你以后是否使用父類的指針或引用來指向或引用這個對象,在構造的那“一瞬間”,總歸要指明要構造對象的具體類型,所以,對象在構造過程中不存在運行時動態綁定的多態行為。
例子,假如A是B的父類,
A* p = new B();
則對于虛擬函數f,可以通過A類的指針p直接調用到B類的函數,這就是運行時的多態:
p->f();
B類的對象卻必須通過“A* p = new B();”來構造,顯然不能通過“A* p = new A();”來構造一個B類對象——這是荒唐的,這只能構造一個A類的對象。所以構造函數虛擬無意義。
但析構函數就不同了,p明明是個A類的指針,如果析構函數不是虛擬的,那么,你后面就必須這樣才能安全的刪除這個指針:
delete (B*)p;
但如果構造函數是虛擬的,就可以在運行時動態綁定到B類的析構函數,直接:
delete p;
就可以了。這就是虛析構函數的作用。而事實上,在運行時,你并不是總是能知道p所指對象的實際類型從而進行強制轉換,所以,C++語言既然要支持多態,也就必須支持虛擬析構。
編譯器總是根據類型來調用類成員函數。但是一個派生類的指針可以安全地轉化為一個基類的指針。這樣刪除一個基類的指針的時候,C++不管這個指針指向一個基類對象還是一個派生類的對象,調用的都是基類的析構函數而不是派生類的。如果你依賴于派生類的析構函數的代碼來釋放資源,而沒有重載析構函數,那么會有資源泄漏。
所以建議的方式是將析構函數聲明為虛函數。如果你使用MFC,并且以CObject或其派生類為基類,那么MFC已經為你做了這件事情;CObject的析構函數是虛函數。一個函數一旦聲明為虛函數,那么不管你是否加上virtual 修飾符,它在所有派生類中都成為虛函數。但是由于理解明確起見,建議的方式還是加上virtual 修飾符。
C++不把虛析構函數直接作為默認值的原因是虛函數表的開銷以及和C語言的類型的兼容性。有虛函數的對象總是在開始的位置包含一個隱含的虛函數表指針成員。如果是對于MFC類CPoint和CSize這樣的小型類,增加一個指針就增加了很多內存占用,而且使得其內存表示和基類POINT和SIZE不一致。如果兩個類的內存表示一致,那么這樣你可以安全地把一個類的指針或數組當作另一個類的指針或數組使用。
通過基類的指針去刪除派生類的對象,而基類又沒有虛析構函數時,結果將是不可確定的。這意味著編譯器生成的代碼將會做任何它喜歡的事:重新格式化你的硬盤,給你的老板發電子郵件,把你的程序源代碼傳真給你的對手,無論什么事都可能發生。(實際運行時經常發生的是,派生類的析構函數永遠不會被調用。
實現虛函數需要對象附帶一些額外信息,以使對象在運行時可以確定該調用哪個虛函數。對大多數編譯器來說,這個額外信息的具體形式是一個稱為vptr(虛函數表指針)的指針。vptr指向的是一個稱為vtbl(虛函數表)的函數指針數組。每個有虛函數的類都附帶有一個vtbl。當對一個對象的某個虛函數進行請求調用時,實際被調用的函數是根據指向vtbl的vptr在vtbl里找到相應的函數指針來確定的。
虛函數實現的細節不重要,但基類中最好成績要有.此時就有基本的一條是,無故的聲明虛析構函數和永遠不去聲明一樣是錯誤的。實際上,很多人這樣總結:當且僅當類里包含至少一個虛函數的時候才去聲明虛析構函數。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。