理解虚方法
1、什么是虛方法?
考慮Animal* pa = new Dog(); pa表面類型是Animal,實際類型是Dog。可以理解為,pa說,我指向Animal,說法是對的。但是不具體,實際上,pa指向Dog。pa->Say()是虛方法,在編譯期,編譯器只知道pa 的表面類型,不知道該調用Animal 的Say方法還是Dog 的Say方法,所以才叫做“虛方法”。只有在運行期,才根據pa 的真實類型,確定調用哪個方法。這就是虛方法。
2、為什么需要虛方法?它解決了什么問題?
簡單說,就是為了面向接口編程,庫的提供者暴露接口,隱藏實現。庫的使用者不需要知道內部的實現細節。
3、它是如何解決的?
虛方法也就是運行時多態。要實現運行時多態,怎么辦?思考,運行時多態的本質特征是,相同的方法名,卻導致調用不同的方法。因此,這里需要加一個間接層,內部封裝,暴露接口。現在簡單講一下,目前的C++是如何實現多態的?
考慮,Animal類有一個虛方法表(可認為是一個數組,元素是方法指針),里面有100個虛方法,Dog類會整體拷貝Animal類的虛方法表,對于Dog類重寫的方法,在虛方法表的對應位置偷梁換柱,換上重寫后的方法,對于Dog類新增的虛方法,在虛方法表中后面加上。
類的對象內存中只有,實例字段和vptr兩塊內容,其中vptr指向類的虛方法表。假設Animal對象的內存布局為:age,name,vptr,Animal對象的vptr指向Animal類的虛方法表。Dog對象的內存布局為age,name,vptr,color,Dog對象的vptr指向Dog類的虛方法表。
考慮pa->Say()虛方法的調用,編譯器知道Say是虛方法,通過vptr間接調用,在運行期才能確定下來。pa實際指向Dog對象,編譯器把pa指向內容當作Animal對象來解釋,這有沒有問題呢?
在上面對象的內存布局看到,Animal對象和Dog對象在前面部分是一樣的,Dog對象追加了一些內存。把pa指向的內容當作Animal對象解釋,取第三個字段vptr,也就是Dog 對象的vptr,我們知道Do對象的vptr,指向Dog類的虛方法表。也就是說,會調用Dog類重寫的虛方法。
4、用法
在父類中,為了表明是虛方法,在方法前加上virtual,子類重寫續方法。子類中的方法不需要在說明,是virtual,會自動生成為virtual,但是,為了直觀,建議子類中也使用virtual。
5、注意事項
a、沒有虛方法的類,這個類也沒有對應的虛方法表,也可能是對應的虛方法表為空,類的對象也就沒有vptr。因此,不要隨便添加無用的虛方法,否則,會導致對象變大,要多一個vptr字段。
b、只有表面類型和真實類型不同的情況下,才存在多態。也就是說,只有指針或者引用才存在多態。為啥?對于類對象而言,真實類型,就是聲明的表面類型。子類對象賦值給父類對象,會出現對象切割,也就是把子類的部分切割掉,父類的部分整體拷貝。
c、構造方法和析構方法中,不要調用虛方法,因為達不到預期的效果。為啥?子類構造方法是在父類構造方法完成的基礎上進行的。從這個角度講,構造析構可以類比穿衣脫衣。穿衣時,先穿內衣,再穿外套。脫衣時,先脫外套,再脫內衣。
考慮,在父類構造方法內調用虛方法,期望調用子類重寫的方法。這個是不行的,為啥?在父類構造方法中,當前對象不是一個完整的子類對象,還沒有子類部分,也就是說,當前情況下的真實類型就是父類對象,當然不可能有多態效果。
那么析構方法呢?在父類的析構方法中調用虛方法,期望調用子類重寫的虛方法。這個也是不行的,為啥?從穿衣脫衣的例子中,我們知道在父類的析構方法中,子類部分已經銷毀了。
6、C++虛方法實現的局限性
通過上面的分析,知道C++虛方法的實現,是子類和父類都保存一個虛方法表,這就導致同樣的值,存儲多次,耗費多余的內存,特別是極端的情況下,頂層父類有許多虛方法,下面有衍生出一大堆的子類,每一個類都有一個很大的虛方法表。同樣的值,存儲多次是很愚蠢的。
C++虛方法實現的優點也是很明顯的,由于子類和父類的虛方法表,在位置上一一對應,要么是同樣的方法指針,要么二者是重寫關系,當然也有可能是子類新增加了一些虛方法,對應的父類位置為空。這樣,就使得方法的查詢效率很高,根據下標直接定位。
總結
- 上一篇: 【Python】TypeError: a
- 下一篇: Adapter模式