为什么我们需要虚拟表?

leg*_*o69 10 c++

我正在寻找有关虚拟表的一些信息,但找不到任何易于理解的内容.
有人可以给我一些很好的例子(不是来自维基),有解释或链接吗?

Fre*_*abe 10

如果没有虚拟表,则无法使运行时多态性起作用,因为所有对函数的引用都将在编译时绑定.一个简单的例子

struct Base {
  virtual void f() { }
};

struct Derived : public Base {
  virtual void f() { }
};

void callF( Base *o ) {
  o->f();
}

int main() {
  Derived d;
  callF( &d );
}
Run Code Online (Sandbox Code Playgroud)

在函数内部callF,您只知道o指向一个Base对象.但是,在运行时,代码应该调用Derived::f(因为Base::f是虚拟的).在编译时,编译器无法知道o->f()调用将执行哪个代码,因为它不知道o指向哪个代码.

因此,您需要一种称为"虚拟表"的东西,它基本上是一个函数指针表.具有虚函数的每个对象都有一个"v表指针",指向其类型对象的虚拟表.

callF上面函数中的代码只需要查找Base::f虚拟表中的条目(它根据对象中的v表指针找到),然后调用表条目指向的函数.这可能是,Base::f但它也可能指向其他东西 - Derived::f例如.

这意味着由于虚拟表,您可以在运行时具有多态性,因为调用的实际函数是在运行时通过查找虚拟表中的函数指针然后通过该指针调用函数来确定的 - 而不是调用直接的功能(非虚函数的情况).

  • 也许值得一提的是 v 表中 `Base::f` 的查找是如何工作的:对于所有类型兼容的类,指向函数 `f()` 的指针始终存储在相同的偏移量处(根据 [Wikipedia] (https://en.wikipedia.org/wiki/Virtual_method_table))。例如,如果在“Base”的 v 表中,指向“Base::f()”的指针存储在偏移量“+4”处,那么在“Derived”的 v 表中,指向“Derived::f(”的指针)` 也存储在偏移量 `+4` 处。 (6认同)

mdm*_*dma 8

虚函数表是一个实现细节——它是编译器在类中实现多态方法的方式。

考虑

class Animal
{
   virtual void talk()=0;
}

class Dog : Animal
{
   virtual void talk() {
       cout << "Woof!";
   }
}

class Cat : Animal
{
   virtual void talk() {
       cout << "Meow!";
   }
}
Run Code Online (Sandbox Code Playgroud)

现在我们有

   A* animal = loadFromFile("somefile.txt"); // from somewhere
   animal->talk();
Run Code Online (Sandbox Code Playgroud)

我们如何知道talk()调用的是哪个版本?动物对象有一个表,指向与该动物一起使用的虚函数。例如,talk可能在第 3 个偏移处,如果还有另外两个虚方法:

   dog
   [function ptr for some method 1]
   [function ptr for some method 2]
   [function ptr for talk -> Dog::Talk]

   cat
   [function ptr for some method 1]
   [function ptr for some method 2]
   [function ptr for talk -> Cat::Talk]
Run Code Online (Sandbox Code Playgroud)

当我们有一个 实例时Animnal,我们不知道talk()要调用哪个方法。我们通过查看虚拟表并获取第三个条目来找到它,因为编译器知道它对应于talk指针(编译器知道 Animal 上的虚拟方法,因此知道 vtable 中指针的顺序。)

给定一个 Animal,要调用正确的 talk() 方法,编译器会添加代码来获取第三个函数指针并使用它。这然后指向适当的实现。

对于非虚拟方法,这不是必需的,因为可以在编译时确定被调用的实际函数——只有一个可能的函数可以被非虚拟调用调用。

  • 当然 - 想象动物是从文件加载的,或者根据用户输入创建不同的动物。目的是编译器无法知道我们有什么类型的动物。 (2认同)

小智 5

要回答您的标题问题 - 您没有,并且C++标准没有指定必须提供一个.你想要的是能够说:

struct A {
  virtual ~A() {}
  virtual void f() {}
};

struct B : public A {
  void f() {}
};

A * p = new B;
p->f();
Run Code Online (Sandbox Code Playgroud)

并且调用B :: f而不是A :: f.虚函数表是实现这一点的一种方式,但对于普通的C++程序员来说坦率地说并不感兴趣 - 在回答这样的问题时我只考虑过它.