朋友最近發郵件問我兩個問題。內容如下(為了更適合閱讀,我做了簡單修改。譯者在此基礎上又做了修改):
我在C++的多繼承上遇到了很大麻煩。
如圖1,A、B1和B2為純抽象類;C從B1、B2多繼承,且實現了全部父類的抽象方法。
圖1
現在:
C* p = new C;
p->Method_of_A(); //從B1、B2都能得到被調用方法,為什麼編譯器不報“二義性”(ambiguity)錯誤呢?
而按圖2結構實現繼承關系後:
圖2
B4* p = new C;
p->Method_of_A();
編譯器(VC++)認為有二義性。經調試我發現編譯過程中使用了“adjustor thunk”(譯者注:具體請參看http://blog.sina.com.cn/u/491874bb010004xq或Stan Lippman的《Inside the C++ Object Model》)。希望您能解答這兩個問題,以幫助我更好理解C++(更確切的說是VC++)中的MI(多繼承)機制。
好,我們深入研究下這個問題。
上述編譯器行為的差異,與繼承關系的復雜度、vtable以及adjustor thunk並無直接關系,它其實就是一個名字查找(name lookup)過程(以本例而言,就是查找方法“Method_of_A”)。
在C++中,函數編譯時檢查過程如下:
第一步,執行名字查找(name lookup):在調用類中查找,並生成候選列表;若候選列表為空,再擴大查找范圍(如名字空間內,或父類);如此循環。如果最終無結果,那麼抱歉,就會提示你“名字未能找到”;否則,編譯器跳到第二步。
第二步,執行重載辨別(overload resolution):如果第一步得到的候選者個數大於一,編譯器將以傳遞給函數的參數及其類型為依據,嘗試找到最佳答案。如果無法據此確定最優者,就會報告“存在二義性調用”。
第三步,可見性檢查(accessibility checking):編譯器檢查是否可真正執行調用(比如,被調用函數是否是私有的)。
總而言之一句話,上述三個過程,都實現於對象的靜態類型基礎上,與實例無關。
問題1:
C* p = new C;
p->Method_of_A();
名字查找就只會在C中進行,根本不會達到A,實際就是直接調用C::Method_of_A.
而在問題2中:
B4* p = new C;
p->Method_of_A();
使用的對象類型是B4,而B4本身沒有提供Method_of_A,因此會到其父類B1、B2中查找,結果找到兩個,且不能通過重載辨別實現優化,因此報告存在二義性。