2010-06-09 93 views
6

我正在寻找一些关于虚拟表格的信息,但找不到任何易于理解的东西。
有人可以给我很好的例子(不是来自Wiki,请解释或链接?)为什么我们需要虚拟桌面?

+1

*你*不需要它,但编译器。 – EJP 2017-02-13 23:53:05

回答

6

没有虚拟表格,你将无法使运行时多态性工作,因为所有对函数的引用都将在编译时被绑定。一个简单的例子

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

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

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

int main() { 
    Derived d; 
    callF(&d); 
} 

功能callF里面,你只知道o指向一个Base对象。但是,在运行时,代码应该调用Derived::f(因为Base::f是虚拟的)。在编译时,编译器无法知道o->f()调用将执行哪个代码,因为它不知道o指向哪个代码。

因此,你需要一个叫做“虚拟表”的东西,它基本上是一个函数指针表。每个具有虚函数的对象都有一个“虚表指针”,它指向虚表的类型对象。

callF函数上面的代码然后只需要查找为Base::f在虚拟表中的条目(它发现基于所述v表指针中的对象),并且然后它调用函数表条目指着。那可能Base::f但它也可能指向其他 - Derived::f,例如。

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

1

简短回答:虚函数调用basePointer-> f()意味着不同的事情取决于basePointer的历史。如果它指向真的是派生类的东西,则会调用一个不同的函数。

为此,编译器做了一个简单的函数指针游戏。要为不同类型调用的函数地址存储在虚拟表中。

虚拟表不仅用于函数指针。 RTTI机制将其用于运行时类型信息(获取由某个基本类型的地址引用的对象的实际类型)。

一些新的/删除实现会将对象大小存储在虚拟表中。

Windows COM编程使用虚拟表来破解它并将其作为接口推送。

5

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

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

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

A * p = new B; 
p->f(); 

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

+0

有关替代方法的示例,python将方法和属性直接存储在对象中。它因此完成了这种行为,但不使用“虚拟”表格,尽管它非常相似。 – 2010-06-09 09:46:28

+0

@Matthieu M,python与C++完全相同 - 它存储对方法对象的一些引用(在用指针实现的c-python中),而C++存储方法的地址。区别主要在于python表按每个对象而不是按类存储,因为python允许在运行时添加属性和方法。 – gnud 2010-08-24 09:09:59

+0

我真的不同意:在C++中,虚拟表没有指向函数存储的指针的实际信息,编译器只知道所需的方法在给定的索引处。另一方面,在python中,属性和方法(通常)被存储在一个字典中,你可以按名称查找。这是实现中的一个主要区别,它使python以性能为代价获得更大的灵活性。 – 2010-08-24 16:29:32

0

假设PlayerMonster继承自定义虚拟name()操作的抽象基类Actor。进一步假设你有要求的演员,他的名字的函数:

void print_information(const Actor& actor) 
{ 
    std::cout << "the actor is called " << actor.name() << std::endl; 
} 

这是不可能在编译时推断演员是否会真正成为一名球员或一个怪物。由于它们有不同的方法,决定调用哪个方法必须推迟到运行时。编译器为每个角色对象添加附加信息,以便在运行时作出此决定。

在每一个编译器,我知道,这些额外的信息是一个指针(通常称为的vptr)函数指针(通常称为VTBL)特定于具体类的表。也就是说,所有玩家对象共享相同的虚拟表,其中包含指向所有玩家方法的指针(对于怪物也是如此)。在运行时,通过从应该在其上调用方法的对象的vptr所指向的vtbl中选择方法来找到正确的方法。

1

虚函数表是一个实现细节 - 这是编译器如何在类中实现多态方法。

考虑

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

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

class Cat : Animal 
{ 
    virtual void talk() { 
     cout << "Meow!"; 
    } 
} 

现在我们必须

A* animal = loadFromFile("somefile.txt"); // from somewhere 
    animal->talk(); 

我们怎么知道这些talk()版本被称为?动物对象有一张表格,指向与该动物一起使用的虚拟功能。例如,talk可能是在第三偏移,如果有两个其他的虚拟方法:

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] 

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

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

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

+0

A * animal = ....; //来自某处 animal-> talk(); 在这一行可以更具体,谢谢 – lego69 2010-06-09 09:47:41

+1

当然 - 设想动物从文件加载,或根据用户输入创建不同的动物。目的是编译器无法知道我们有什么类型的动物。 – mdma 2010-06-09 10:05:34