2012-03-02 78 views
3

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()呼叫决心?


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

+1

请认识到vtable是一个实现细节。语言中对虚拟表格的形式,功能甚至是存在都没有要求。你的编译器可能碰巧使用了一个,它可能看起来像你的图片。但是,再一次,它可能不会。 – 2012-03-02 20:18:13

+0

虚拟派生时你也会得到一个vptr。除此之外,为什么创建一个vptr/vtable,当你不需要它? – PlasmaHH 2012-03-02 21:54:04

回答

9

[如果]没有虚拟表(表示没有指向函数所在的指针),b.function1()调用将如何解决?

编译器内部有一个“指针”,因为它解析和分析代码。编译器决定函数将在哪里生成,所以它知道如何调用所述函数应该解决。与链接器一起,这一切都在构建过程中整齐地进行。

原因,这并不为virtual职能的工作是函数调用取决于只在运行时已知的类型;事实上,相同的函数指针在编译器编写的虚拟表中逐字呈现。只是在这种情况下,有多种可供选择,并且在编译器完全不再参与之后,它们不能长时间选择(可能有几个月甚至几年!)。

+1

嘿,我的第2000个回答! :d – 2012-03-04 02:54:30

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()它会选择基于该静态类型的过载(编译时类型)的第一个参数:

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 

编译器和将决定功能在每种情况下都会在编译时调用,并且可以发出具有固定功能地址或地址偏移量的汇编代码。运行时虚拟表的概念在这里是无意义的。

现在,当我说要将方法版本等同于自由函数版本时,您会发现在汇编语言级别上,它们的对应关系为。唯一的区别将是所谓的mangled名称,它在编译的函数名称中对类型进行编码并区分重载的函数。你方法名前通过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指针的静态类型,它可以在编译时执行的编译器分派。

尽管虚拟功能使用相同的主叫语法非虚成员函数,你实际上解引用所述对象的指针;具体而言,您将取消引用该对象的类的虚拟表的指针。