一直以為自己對多態和繼承已經比較瞭解,當遇到虛繼承的時候,發現有點犯暈,想不通了,於是在微博上向幾個大神請教,很快得到了他們的回複,高興之情無以言表。之後自己查了一些資料,結合大神的回複,在這裡做一下簡單的記錄。
我的問題如下:
為什麼虛繼承類的sizeof要大些啊,是因為虛繼承中,子類有指向父類的指標和指向父類的虛函數表的指標嗎,比非虛繼承多了這兩個指標? @左耳朵耗子 @簡悅雲風 @GeniusVczh
@GeniusVczh:調用的時候給的this和函數實際需要的this的指標不一定是一樣的,多重繼承的時候已經這樣了。再加上你還有virtual繼承,所以需要很多描述。
V福爾摩斯 回複 @GeniusVczh:看了B(第三個圖中的B)的記憶體布局,的確有vfptr和vbptr兩個指標
簡悅雲風:和編譯器實現有關。實現上虛繼承更象是組合,因為它可以被菱形繼承而只有一份,所以加上一個額外指標引用這個對象。沒有虛函數時不產生虛表,所以 2 裡就是 a 對象加額外指標。3裡面 b 有虛表,就再加一個虛表指標。字數不夠不吐槽了。
左耳朵耗子:1) int a :4位元組;2)虛函數增加一個虛表指標:4位元組。3)虛繼承還會增加一個指標:4位元組。但是為什麼最後會是16個字呢?你是在用VC++吧?看一下我的這篇文章(http://t.cn/a1lMjd 最後一個樣本)你會知道VC++的物件版面配置是有點詭異。G++下應該是正常的。
看了簡悅雲風和左耳朵耗子兩個大神的回複之後,自己覺得還是有點暈,於是看了一下《Effective C++》和《More Effective C++》,在這裡做一下記錄。
多態的實現原理:
1:含有虛方法的類都有一個虛函數表
2:子類的虛方法會覆蓋父類對應的虛方法
3:含有虛方法的類的每個執行個體都有一個指向虛方法表的指標,如果虛繼承的話可能會有多個
4:根據3中的指標調用虛方法表中對應的虛方法
多態的實現差不多就是上面幾點。面試中經常遇到的就是調用哪個方法的問題,一句話告訴你是怎麼調用的:在繼承關係中,非虛方法調用指標類型的方法;虛方法調用指標所指的物件類型的方法。非虛方法和預設參數都是靜態繫結,在繼承關係中只跟指標類型有關,跟指標所指的對象的實際類型無關。還有一點就是非虛方法就像C方法一樣,不用太在意,證明非虛方法就像C方法的一個方式就是,用一個null 指標調用一個非虛方法,只要這個對象沒有用到對象的資料,就不會有任何問題。
再來看看我發問的哪個圖,為什麼圖3中sizeof(B)=16,於是用VS內建的工具看了一下B的記憶體布局,如:
查看對象記憶體布局的VS命令:cl [filename].cpp /d1reportSingleClassLayout[className]
看到這個圖,其實還是不太好理解,int a佔4位元組,B有自己的虛函數表,虛函數指標佔4位元組,另外多出一個vfptr和vbptr,那就只能這樣理解了:vfptr指向父類的虛函數表,B每多虛繼承一個類,就多一個vfptr,不信你可以試試,vbptr指向A,但是每多虛繼承一個類,並不多出一個vbptr,這是和解呢?
@pop_Atry:幹嘛不把B到A的位移量放到虛表裡面,何必為每個對象添加一個額外引用?
@簡悅雲風:回複@pop_Atry: 1. 效能原因; 2.有的編譯器的確是放位移量的.
風神一語中的,但如果不按照@pop_Atry,就是我們現在看到的,B變大了。
妹的,不同的編譯器有不同的實現,咋們討論這個問題有什麼意義呢,千萬不要把不同編譯器編譯的代碼放到同一個程式中啊,啊哥。
虛擬繼承的出現就是為瞭解決重複繼承中多個間接父類的問題的,保證每個父類都只有一份。
Effective C++的作者建議盡量避免多繼承,如果不能避免也要避免菱形繼承,各種莫名其妙的複雜啊。所以說多繼承也就算了,還許多(虛多)繼承,那就要搞死人了。
更多瞭解請看左耳朵耗子的相關部落格:http://blog.csdn.net/haoel/article/details/3081385