vtable:底层算法
vtable: Underlying algorithm
我对 vtables 的理解是,如果我有一个 class 带有虚函数 speak() 的 Cat 以及 subclasses Lion 和 HouseCat,则有一个映射 speak() 的 vtable到每个 Subclass 的正确实现。所以打个电话
cat.speak()
编译为
cat.vtable[0]()
即在vtable位置0处查找,并调用该位置的函数指针。
我的问题是:多重继承会发生什么?
让我们添加一个 class 宠物。 Pet 有虚函数 speak() 和 eat()。 HouseCat 扩展了 Pet,而 Lion 没有。现在,我需要确保
pet.eat()
编译为
pet.vtable[1]()
即vtable[0]需要speak()。 Pet.eat 需要是 slot 1。那是因为 cat.speak() 需要访问 vtable 中的 slot 0,如果对于 HouseCat,slot 0 恰好是 eat,这将是非常错误的。
编译器如何确保 vtable 索引适合在一起?
规范没有设置任何内容,但通常编译器会为每个直接非虚拟基 class 生成一个 vtable,再加上一个用于派生 class 的 vtable - 然后是 vtable第一个基础 class 和派生 class 的 vtable 将被合并。
更具体地说,编译器在构造 classes 时生成的内容:
猫
[vptr | Cat fields]
[0]: speak()
宠物
[vptr | Pet fields]
[0]: eat()
狮子
[vptr | Cat fields | Lion fields]
[0]: speak()
家猫
[vptr | Cat fields | vptr | Pet fields | HouseCat fields]
[0]: speak() [0]: eat()
编译器在调用/强制转换时生成的内容(变量名是静态类型名):
cat.speak()
obj[0][0]()
- 适用于 Cat、Lion 和 HouseCat"Cat" 部分
pet.eat()
obj[0][0]()
- 对 Pet 和 HouseCat"Pet" 部分有效
lion.speak()
obj[0][0]()
- 适用于 Lion
houseCat.speak()
obj[0][0]()
- 适用于 HouseCat"Cat" 部分
houseCat.eat()
obj[Cat size][0]()
- 适用于 HouseCat 的 "Pet" 部分
(Cat)houseCat
obj
(Pet)houseCat
obj + Cat size
所以我猜让您感到困惑的关键是 (1) 多个 vtable 是可能的,并且 (2) upcasts 实际上可能 return 一个不同的地址。
我对 vtables 的理解是,如果我有一个 class 带有虚函数 speak() 的 Cat 以及 subclasses Lion 和 HouseCat,则有一个映射 speak() 的 vtable到每个 Subclass 的正确实现。所以打个电话
cat.speak()
编译为
cat.vtable[0]()
即在vtable位置0处查找,并调用该位置的函数指针。
我的问题是:多重继承会发生什么?
让我们添加一个 class 宠物。 Pet 有虚函数 speak() 和 eat()。 HouseCat 扩展了 Pet,而 Lion 没有。现在,我需要确保
pet.eat()
编译为
pet.vtable[1]()
即vtable[0]需要speak()。 Pet.eat 需要是 slot 1。那是因为 cat.speak() 需要访问 vtable 中的 slot 0,如果对于 HouseCat,slot 0 恰好是 eat,这将是非常错误的。
编译器如何确保 vtable 索引适合在一起?
规范没有设置任何内容,但通常编译器会为每个直接非虚拟基 class 生成一个 vtable,再加上一个用于派生 class 的 vtable - 然后是 vtable第一个基础 class 和派生 class 的 vtable 将被合并。
更具体地说,编译器在构造 classes 时生成的内容:
猫
[vptr | Cat fields] [0]: speak()
宠物
[vptr | Pet fields] [0]: eat()
狮子
[vptr | Cat fields | Lion fields] [0]: speak()
家猫
[vptr | Cat fields | vptr | Pet fields | HouseCat fields] [0]: speak() [0]: eat()
编译器在调用/强制转换时生成的内容(变量名是静态类型名):
cat.speak()
obj[0][0]()
- 适用于 Cat、Lion 和 HouseCat"Cat" 部分
pet.eat()
obj[0][0]()
- 对 Pet 和 HouseCat"Pet" 部分有效
lion.speak()
obj[0][0]()
- 适用于 Lion
houseCat.speak()
obj[0][0]()
- 适用于 HouseCat"Cat" 部分
houseCat.eat()
obj[Cat size][0]()
- 适用于 HouseCat 的 "Pet" 部分
(Cat)houseCat
obj
(Pet)houseCat
obj + Cat size
所以我猜让您感到困惑的关键是 (1) 多个 vtable 是可能的,并且 (2) upcasts 实际上可能 return 一个不同的地址。