来自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的虚拟表:
上面的虚拟表是有道理的。在所有对象都需要一种方法来调用函数之后,需要使用函数指针来查找它们。
我不明白为什么只有在使用虚拟功能时才需要这样做?我肯定错过了一些东西,因为虚拟表不直接依赖于虚函数。
例如,如果使用的代码是
class Base
{
public:
void function1() {};
void function2() {};
};
...
Base b;
b.function1();
并且没有虚拟表(意味着没有指向函数所在位置的指针),b.function1()
调用将如何解析?
或者在这种情况下我们有一个表,只是它不被称为虚拟表?在这种情况下,会出现一个问题,为什么我们需要一种新的虚函数表?
答案 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 类型,它可以在编译时执行。
虽然虚函数使用与非虚拟成员函数相同的调用语法,但 实际上是取消引用对象指针;具体来说,您将取消引用指向对象类的虚拟表的指针。