2010-06-09 60 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