這可以用輕量級C++來表示,如果你發現它比程序集更可讀(我願意)。我會將自己限制在C(主要),並且只是添加繼承以避免大量的投射。
爲了清楚起見,實現細節將以__
爲前綴。請注意,這些標識符通常是爲實現保留的,因此您通常不應該在您的程序中使用它們。
的類型安全虛擬調度方法。
注:僅限於簡單的繼承(單鹼基,沒有虛繼承)
,讓我們創建Animal
類。
struct __AnimalTableT;
struct Animal { __AnimalTableT const * const __vptr; int height; }
void AnimalInit(Animal* a, int height) {
a->height = height;
}
我們預留空間的指針,在動物的虛擬表,並表示該方法作爲外部功能使this
明確。
接下來,我們「創建」虛擬表。請注意,C中的數組需要由相似的元素組成,因此這裏我們將使用稍高級別的方法。
struct __AnimalTableT {
typedef void (*InitFunction)(int);
InitFunction Init;
};
static __AnimalTableT const __AnimalTable = { &AnimalInit };
現在,讓我們創建一個牛:
struct Cow: Animal {};
void CowInit(Animal* a, int height) {
Cow* c = static_cast<Cow*>(a);
c->height = height;
}
和相關的表:
// Note: we could have new functions here (that only Cow has)
// they would be appended after the "Animal" part
struct __CowTableT: __AnimalTableT {};
static __CowTableT const __CowTable = { &CowInit };
和使用:
typedef void (*__AnimalInitT)(Animal*,int);
int main() {
Cow cow = { &__CowTable, 0 };
__AnimalInitT const __ai = cow.__vptr->Init;
(*__ai)(&cow, 5);
}
真的嗎?
真正的使用稍微複雜一些,但建立在相同的想法上。
正如你可以注意到,很奇怪CowInit
需要一個Animal*
指針作爲它的第一個參數。問題是你需要一個兼容的函數指針類型和原來的重載方法。在線性繼承的情況下,這並不重要,但在多繼承或虛擬繼承的情況下,事情變得非常忙碌,並且Cow
的子部分可能不會在開始時展開,從而導致指針調整。
在現實生活中,我們的thunk:
好了,我們可以改變的CowInit
簽名更自然:
void CowInit(Cow* cow, int height);
然後,我們的「橋」通過創建一個空白一個「咚」,使適應:
void __CowInit(Animal* a, int height) {
CowInit(static_cast<Cow*>(a), height);
}
static __CowTableT const __CowTable = { &__CowInit };
在現實生活中,我們有表:
另一種說法是,結構的使用非常好,但我們在這裏討論的是實現細節,因此不需要精確。在一般情況下,編譯器,因此使用普通的陣列,而不是一個結構:
typedef (void)(*__GenericFunction)();
static __GenericFunction const __AnimalTable[] = {
__GenericFunction(&AnimalInit)
};
static __GenericFunction const __CowTable[] = {
__GenericFunction(&__CowInit)
};
這會稍微改變呼叫:使用索引而不是屬性名的,你需要轉換回相應的功能類型。
typedef void (*__AnimalInitT)(Animal*,int);
int main() {
Cow cow = { &__CowTable, 0 };
// old line: __AnimalInitT const __ai = cow.__vptr->Init;
__AnimalInit const __ai = __AnimalInit(cow.__vptr[0]);
(*__ai)(&cow, 5);
}
正如您所看到的,表的使用實際上是一個實現細節。
這裏真正重要的一點是引入一個thunk來調整函數簽名。請注意,thunk是在創建派生的類(此處爲Cow)表時創建的。在我們的例子中,這是沒有必要的,因爲在低級別這兩個對象都有相同的地址,所以我們可以不用,而且一個智能編譯器不會生成它並直接採取&CowInit
。
它應該是'Animal * a;'你爲什麼不通過生成彙編代碼來看看自己?除此之外'vtable''vptr'機制具有很高的實現特定性。 –
@Als的任何一般想法都會有所幫助。事情是我不擅長閱讀彙編代碼。作業有一組生成彙編代碼的函數。 –
這個編輯過的新代碼是這樣做的。 –