如何在 class 中的其他 vtable 指针中选择 vtable 指针?

How vtable pointer is chosen among other vtable pointers in a class?

我对 virtual inheritance 很感兴趣,这件事给我带来了神秘感。让我们考虑一个 virtual inheritance:

的例子
struct Base {
    virtual void v() { std::cout << "v"; }
};

struct IntermediateDerivedFirst : virtual Base {
    virtual void w() { std::cout << "w"; }
};

struct IntermediateDerivedSecond : virtual Base {
    virtual void x() { std::cout << "x"; }
};

struct Derived : IntermediateDerivedFirst, IntermediateDerivedSecond {
    virtual void y() { std::cout << "y"; }
};

最后,Derived 应该是这样的:

 --------
|[vtable]| -----> [ vbase offset 20               ]
|[vtable]|---     [ top offset    0               ]
|[vtable]|- |     [ Derived typeinfo              ]
 --------  ||     [ IntermediateDerivedFirst::w() ]
           ||     [ Derived::y()                  ]
           ||
           |----> [ vbase offset 12               ]
           |      [ top offset   -8               ]
           |      [ Derived typeinfo              ]
           |      [ IntermediateDerivedSecond::x()]
           |
           -----> [ vbase offset 0               ]
                  [ top offset   -20             ]
                  [ Derived typeinfo             ]
                  [ Base::v()                    ]

所以,从字面上看,virtual 继承将最基础 class 的 vtable 移动到末尾,正如我们所见 - vtables 对于 IntermediateDerivedFirst, IntermediateDerivedSecond 不包含 Basev() 方法的地址。好的,那么,我们可以看到 class 的 vtable 很少。 让我们考虑一个代码:

IntermediateDerivedFirst* fb = new Derived;
fb->v();
delete fb;

此调用仍然有效,但是 IntermediateDerivedFirstvtable 没有关于 v() 方法的信息,它似乎使用了一些这里很神奇,它使用第三个 vtable 指针来调用 v()。 那么,编译器如何选择需要的vtable指针来获取被调用函数的地址呢?

Bjarne Stroustroup 写了一篇关于使用 C++ 解决多重继承中的“钻石问题”的详细论文: Stroustrup, B Fall 1989, 'Multiple Inheritance for C++'. Computing Systems, Vol. 2 No. 4, p. 367-395

在两个 IntermediateDerived classes 中都声明了 'virtual Base';您保证只会获得公共基础的单个实例 class.

来自C++ Language / Classes / Derived classes

For each distinct base class that is specified virtual, the most derived object contains only one base class subobject of that type, even if the class appears many times in the inheritance hierarchy (as long as it is inherited virtual every time).

也就是说,编译器会为每一个提供一个实例:

  • 派生
  • IntermediateDerivedFirst
  • IntermediateDerivedSecond
  • 基地

IntermediateDerived_X classes 将在它们的 vtables 中有一个 虚拟指针,用于存储到 Base class。当 IntermediateDerived_X class 尝试访问 Base::v() 时,它使用 vtable 中的 虚拟指针 来查找 Base目的。沿着这条线;如果 Derived 继承了 100 个以上 IntermediateDerived_X class 具有相同 'virtual Base,' 的元素,那么仍然只有一个 Base 实例。 Base::v() 函数的调用使用虚拟指针访问 Base class.

的实例

需要注意的是Base对象只构造一次;当它在 Derived class 中首次初始化时。继承的 classes 将从 Base 到 most derived 构造。反过来;破坏顺序将从最派生到基础 在示例实例化中;调用 'new Derived' 将构造四个 classes 中每一个的单个实例。

基于上面的地址 table:IntermediateDerivedFirst 知道 'vbase offset = 20',这是 Base 对象 vtable 的位置。 IntermediateDerivedSecond 可以用同样的方式访问 Base class (vbase offset = 12);并会产生完全相同的功能。 没有神奇的第三个 vtable,只是指向所有派生 classes 共享的 Base 的 vtable 的指针。

从实现的角度来看;在所有虚拟 class 中添加指向基 class 的单个指针通常比将虚拟 table 复制到每个派生 class 更精通内存。不同的编译器、优化、链接器等可能会以不同的方式实现或更改结果 vtables。但是所有使用虚拟继承的派生 classes 只会指向一个 Base 对象。