8

我对 vtables 的理解是,如果我有一个带有虚函数 speak() 以及子类 Lion 和 HouseCat 的类 Cat,那么就会有一个 vtable 将 speak() 映射到每个子类的正确实现。于是打了个电话

cat.speak()

编译为

cat.vtable[0]()

即在 vtable 位置 0 中查找并在该位置调用函数指针。

我的问题是:多重继承会发生什么?

让我们添加一个类 Pet。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 索引组合在一起?

4

1 回答 1

1

规范没有设置任何内容,但通常编译器会为每个直接的非虚拟基类生成一个 vtable,以及为派生类生成一个 vtable - 然后第一个基类的 vtable 和派生类的 vtable 将是合并。

更具体地说,编译器在构造类时生成的内容:

  • [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]()- 适用于猫、狮子和 HouseCat 的“猫”部分
  • pet.eat()
    • obj[0][0]()- 适用于宠物和 HouseCat 的“宠物”部分
  • lion.speak()
    • obj[0][0]()- 适用于狮子
  • houseCat.speak()
    • obj[0][0]()- 适用于 HouseCat 的“猫”部分
  • houseCat.eat()
    • obj[Cat size][0]()- 适用于 HouseCat 的“宠物”部分
  • (Cat)houseCat
    • obj
  • (Pet)houseCat
    • obj + Cat size

所以我想让你感到困惑的关键是(1)多个vtables是可能的,(2)upcasts实际上可能返回一个不同的地址。

于 2017-05-03T04:18:33.180 回答