为什么只有虚拟功能才需要虚拟表?

时间:2012-03-02 19:25:50

标签: c++ virtual-functions vtable

来自http://www.learncpp.com/cpp-tutorial/125-the-virtual-table/,代码如

class Base
{
public:
    virtual void function1() {};
    virtual void function2() {};
};

class D1: public Base
{
public:
    virtual void function1() {};
};

class D2: public Base
{
public:
    virtual void function2() {};
};

生成类似于http://www.learncpp.com/images/CppTutorial/Section12/VTable.gif的虚拟表: enter image description here

上面的虚拟表是有道理的。在所有对象都需要一种方法来调用函数之后,需要使用函数指针来查找它们。


我不明白为什么只有在使用虚拟功能时才需要这样做?我肯定错过了一些东西,因为虚拟表不直接依赖于虚函数。

例如,如果使用的代码是

class Base
{
public:
    void function1() {};
    void function2() {};
};

...

Base b;
b.function1();

并且没有虚拟表(意味着没有指向函数所在位置的指针),b.function1()调用将如何解析?


或者在这种情况下我们有一个表,只是它不被称为虚拟表?在这种情况下,会出现一个问题,为什么我们需要一种新的虚函数表?

2 个答案:

答案 0 :(得分:9)

  

[If]没有虚拟表(意味着没有指向函数所在位置的指针),b.function1()调用将如何解析?

编译器内部有一个“指针”,因为它正在解析和分析您的代码。编译器决定函数将在何处生成,因此它知道如何解析对该函数的调用。与链接器一致,这一切都在构建过程中整齐地发生。

这对virtual函数不起作用的原因是您调用的函数取决于仅在运行时已知的类型;实际上,相同的函数指针在虚拟表中逐字存在,由编译器写入。只是在这种情况下,有多个可供选择,并且在编译器完全不参与之后,它们不能被选择(读取:可能是几个月甚至几年!)。

答案 1 :(得分:1)

已经有了一个很好的答案,但我会尝试稍微简单一点(虽然更长):

想一下表格的非虚方法

class A
{
public:
  int fn(int arg1);
};

等同于形式的自由函数:

int fn(A* me, int arg1); // overload A

其中me对应于方法版本中的this指针。

如果你现在有一个子类:

class B : public A
{
public:
  int fn(int arg1);
};

这相当于这样的免费功能:

int fn(B* me, int arg1); // overload B

请注意,第一个参数与我们之前声明的自由函数的类型不同 - 函数在第一个参数的类型上重载。

如果您现在有一些代码调用fn(),它将根据第一个参数的 static 类型(编译时类型)选择重载:

A* p;
B* q;
// ...
// assign valid pointer values to p and q
// ...
int a = fn(p, 0); // will call overload A
int b = fn(q, 0); // will call overload B

编译器可以并且将确定在每种情况下在编译时调用的函数,并且可以发出具有固定函数地址或地址偏移量的汇编代码。运行时虚拟表的概念在这里是荒谬的。

现在,当我说方法版本等同于自由函数版本时,你会发现在汇编语言级别,它们等价的。唯一的区别是所谓的错位名称,它编码已编译函数名称中的类型并区分重载函数。您通过p->fn(0)调用方法的事实,即在方法名称之前的第一个参数是纯syntactic sugar - 您实际上不是 取消引用示例中的指针p,即使它看起来像它。您只是将p作为隐式this参数传递。所以,继续上面的例子,

p->fn(0); // will always call A::fn()
q->fn(0); // will always call B::fn()

因为fn是一个非虚方法,意味着编译器会调度this指针的 static 类型,它可以在编译时执行。

虽然虚函数使用与非虚拟成员函数相同的调用语法,但 实际上是取消引用对象指针;具体来说,您将取消引用指向对象类的虚拟表的指针。