10

我读了这个问题:C++ Virtual class继承对象大小问题,并且想知道为什么虚拟继承会在类中产生一个额外的vtable指针。

我在这里找到了一篇文章:https ://en.wikipedia.org/wiki/Virtual_inheritance

它告诉我们:

然而,在一般情况下,这个偏移量只能在运行时知道,......

我不明白这里与运行时相关的内容。完整的类继承层次结构在编译时是已知的。我了解虚拟函数和基指针的使用,但虚拟继承没有这样的事情。

有人可以解释为什么某些编译器(Clang/GCC)使用 vtable 实现虚拟继承以及在运行时如何使用它?

顺便说一句,我也看到了这个问题:vtable in case of virtual inheritance,但它只指向与虚函数相关的答案,这不是我的问题。

4

3 回答 3

16

完整的类继承层次结构在编译时是已知的。

确实如此;因此,如果编译器知道最派生对象的类型,那么它就知道该对象中每个子对象的偏移量。为此,不需要 vtable。

例如,如果BC两者实际上都派生自A,并且都D派生自BC,那么在以下代码中:

D d;
A* a = &d;

D*to的转换A*最多是在地址上添加一个静态偏移量。

但是,现在考虑这种情况:

A* f(B* b) { return b; }
A* g(C* c) { return c; }

在这里,f必须能够接受指向任何B对象的指针,包括一个B对象,该对象可能是一个对象的子D对象或一些其他最派生的类对象。编译f时,编译器不知道B.

如果B对象是最派生的对象,则A子对象将位于某个偏移量处。但是如果B对象是对象的一部分D呢?该D对象仅包含一个A对象,并且不能位于其通常与子对象和对象的偏移处。所以编译器必须为 的子对象选择一个位置,然后它必须提供一种机制,以便某些带有或的代码可以找出子对象的位置。这仅取决于最派生类型的继承层次结构——因此 vptr/vtable 是一种合适的机制。BCADB*C*A

于 2019-08-13T18:07:45.563 回答
4

然而,在一般情况下,这个偏移量只能在运行时知道,......

我不明白,这里与运行时相关的是什么。完整的类继承层次结构在编译时是已知的。

我认为,Wikipedia 上的链接文章通过示例提供了很好的解释。

那篇文章的示例代码:

struct Animal {
  virtual ~Animal() = default;
  virtual void Eat() {}
};

// Two classes virtually inheriting Animal:
struct Mammal : virtual Animal {
  virtual void Breathe() {}
};

struct WingedAnimal : virtual Animal {
  virtual void Flap() {}
};

// A bat is still a winged mammal
struct Bat : Mammal, WingedAnimal {
};

当您关心类型的对象时Bat,编译器可以通过多种方式选择对象布局。

选项1

+--------------+
| Animal       |
+--------------+
| vpointer     |
| Mammal       |
+--------------+
| vpointer     |
| WingedAnimal |
+--------------+
| vpointer     |
| Bat          |
+--------------+

选项 2

+--------------+
| vpointer     |
| Mammal       |
+--------------+
| vpointer     |
| WingedAnimal |
+--------------+
| vpointer     |
| Bat          |
+--------------+
| Animal       |
+--------------+

中包含vpointer的值定义子对象的偏移量。直到运行时才能知道这些值,因为构造函数无法知道主题是还是其他对象。如果子对象是,它不会派生自。这将只是MammalWingedAnimalAnimalMammalBatMonkeyWingedAnimal

struct Monkey : Mammal {
};

在这种情况下,对象布局可能是:

+--------------+
| vpointer     |
| Mammal       |
+--------------+
| vpointer     |
| Monkey       |
+--------------+
| Animal       |
+--------------+

可以看出,从Mammal子对象到Animal子对象的偏移量是由派生自的类定义的Mammal。因此,它只能在运行时定义。

于 2019-08-13T18:13:30.973 回答
1

完整的类继承层次结构在编译器时是已知的。但是所有vptr相关的操作,例如获取虚拟基类的偏移量和发出虚函数调用,都延迟到运行时,因为只有在运行时我们才能知道对象的实际类型。

例如,

class A() { virtual bool a() { return false; } };
class B() : public virtual A { int a() { return 0; } };
B* ptr = new B();

// assuming function a()'s index is 2 at virtual function table
// the call
ptr->a();

// will be transformed by the compiler to (*ptr->vptr[2])(ptr)
// so a right call to a() will be issued according to the type of the object ptr points to
于 2021-04-17T11:19:08.250 回答