2012-03-02 77 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指針的靜態類型,它可以在編譯時執行的編譯器分派。

儘管虛擬功能使用相同的主叫語法非虛成員函數,你實際上解引用所述對象的指針;具體而言,您將取消引用該對象的類的虛擬表的指針。