毫不令人驚訝的是,C++又給了我一個驚訝!
對於被重載的虛函數(overloaded virtual member functions),Visual C++ 並不會依照它們聲明的順序排布在虛表中。
一組(同名的)重載虛函數會按照它們聲明的逆序依次排布在一起,而組與組之間的順序,是由組內最先出現的那個函式宣告的位置決定的,越先聲明,該組越靠前。
試看下面這個例子:
#include <stdio.h>
struct Base {
virtual void a() = 0;
virtual void b() = 0;
virtual void a(int) = 0;
virtual void b(int) = 0;
};
struct Some : public Base
{
virtual void a() { printf("a()/n"); }
virtual void b() { printf("b()/n"); }
virtual void a(int) { printf("a(int)/n"); }
virtual void b(int) { printf("b(int)/n"); }
};
int
main()
{
Some *p = new Some;
void (Some::*a_ptr)() = &Some::a;
void (Some::*ai_ptr)(int) = &Some::a;
void (Some::*b_ptr)() = &Some::b;
void (Some::*bi_ptr)(int) = &Some::b;
printf("%p,%p,%p,%p/n", *(void**)&ai_ptr, *(void**)&a_ptr, *(void**)&bi_ptr, *(void**)&b_ptr);
return 0;
}
Some對象中的vtable的順序是這樣:Some::a(int), Some::a(), Some::b(int), Some::b()。
用VC(VS2005)跑這段程式來驗證一下,輸出是:
00401140,00401150,00401160,00401170
COM的二進位標準要求介面方法按照聲明順序排列在虛表中,VC的做法和COM矛盾,所以不要在COM介面中使用重載虛函數。微軟的說法是,這種行為是當初VC的設計決定的,沒法兒改了,很多老代碼依賴這種行為。
而g++在處理這個問題上的做法是符合COM規範的,當然也符合了我們的直覺,它的輸出是:
00000009,00000001,0000000D,00000005
注意,由於不同編譯器對指向虛擬成員函數的指標的實現各有不同,所以VC和g++輸出的內容差別很大。不用去管它,我們只要給這4個值排個序就足以判斷出它們在虛表中的位置。
最後要奉勸大家的是,即使想通過只暴露純抽象基類的方法來讓二進位C++模組跨編譯器,你也要相當小心!