2009-08-14 169 views

回答

10

簡短回答:虛擬功能是關於在運行時不知道誰叫誰,當從已經編譯好的候選函數中選取一個函數時。函數模板OTOH是在編譯時從調用者的兩端創建任意數量的不同函數(使用在被調用者編寫時甚至可能不知道的類型)。這只是不匹配。稍微長一些的答案:虛擬功能是通過使用額外的間接方式(程序員的通用多用途治療)來實現的,通常作爲功能指針表(所謂的虛擬功能表,通常縮寫爲「vtable」)來實現。如果你正在調用一個虛函數,運行時系統將從表中選擇正確的函數。如果有虛擬功能模板,則運行系統必須使用確切的模板參數來查找已編譯的模板實例的地址。由於類的設計者不能提供任意數量的由無限可能的參數創建的函數模板實例,所以這是行不通的。

9

你會如何構建vtable?理論上講,你可以擁有無​​限數量的模板化成員版本,編譯器在創建vtable時不知道它們會是什麼。

+0

最近downvoted這個人的任何機會也許足夠勇敢地說爲什麼? :) – Troubadour 2009-08-24 20:53:39

+0

Vtable是一個實現細節,在設計語言特性時應該考慮到後端的困難 - 至少,「C++」。 – 2009-09-06 15:51:10

+1

Pavel:很抱歉把它給你,但這就是爲什麼它被禁止。如果你不相信我,請嘗試閱讀Stroustrup的「The C++ Programming Language」。 – Troubadour 2009-09-06 16:39:24

1

我認爲編譯器可以生成vtable偏移量作爲常量(而對非虛擬函數的引用是修正)。

當您編譯對模板函數的調用時,編譯器通常只是在二進制文件中放置一個註釋,有效地告訴鏈接器「請用正確函數的指針替換此註釋」。靜態鏈接器做了類似的事情,一旦代碼加載到內存中並且地址已知,加載器最終會填充該值。這被稱爲fixup,因爲加載程序通過填充所需的數字來「修復」代碼。請注意,爲了生成fixup,編譯器不需要知道該類中還有其他什麼函數,只需知道它想要的函數的名稱即可。

但是,對於虛擬函數,編譯器通常會發出代碼,指出「將vtable指針從對象中取出,向其中添加24,加載函數地址並調用它」。爲了知道你想要的特定虛擬函數的偏移量爲24,編譯器需要知道類中的所有虛函數,以及它們將在vtable中出現的順序。就目前而言,編譯器確實知道這一點,因爲所有的虛擬函數都在類定義中列出。但是爲了在模板化的虛函數中生成虛擬調用,編譯器需要在調用時知道函數模板的實例。它不可能知道這一點,因爲不同的編譯單元可能會實例化函數模板的不同版本。所以它無法確定在vtable中使用的偏移量。

現在,我懷疑編譯器可以通過發出一個整數修正而不是一個常量vtable偏移來支持虛擬函數模板。也就是說,請注意「請填寫虛擬函數的vtable偏移量」。然後,靜態鏈接器可能會在知道哪些實例化可用時(在刪除不同編譯單元中的重複模板實例的地方)填充實際值。但是這會給連接器帶來沉重的工作負擔,無法找出vtable的佈局,目前編譯器自己做。模板是特意指定的,以便讓實現者更容易,希望它們可能在C++ 0x之前實際出現在野外...

因此,我推測這些方面的一些推理導致標準委員會得出結論,虛擬功能模板即使可以實現,也很難實現,因此無法包含在標準中。

請注意,即使在我嘗試閱讀委員會的意見之前,上面也有一些猜測:我不是C++實現的作者,也不是我在電視上播放的。

4

其他答案已經提到虛擬函數通常用C++處理,方法是在對象中指向一個表的指針(vptr)。此表(vtable)包含指向用於虛擬成員的函數的指針以及其他一些內容。

解釋的另一部分是通過代碼擴展在C++中處理模板。這允許明確的專業化。 (埃菲爾 - 我認爲這也是Java和C#的情況,但是我的知識不足以成爲權威),或者允許(Ada)共同處理通用性,不要沒有明確的專業化,但會允許虛擬模板功能,將模板放入庫中並可減少代碼大小。

您可以使用稱爲類型擦除的技術來獲得共享通用性的效果。這是手動執行共享通用性語言的編譯器正在做什麼(至少,其中一些取決於語言,其他實現技術可能是可能的)。這是一個(愚蠢)的例子:

#include <string.h> 
#include <iostream> 

#ifdef NOT_CPP 
class C 
{ 
public: 
    virtual template<typename T> int getAnInt(T const& v) { 
     return getint(v); 
    } 
}; 
#else 
class IntGetterBase 
{ 
public: 
    virtual int getTheInt() const = 0; 
}; 

template<typename T> 
class IntGetter: public IntGetterBase 
{ 
public: 
    IntGetter(T const& value) : myValue(value) {} 
    virtual int getTheInt() const 
    { 
     return getint(myValue); 
    } 
private: 
    T const& myValue; 
}; 

template<typename T> 
IntGetter<T> makeIntGetter(T const& value) 
{ 
    return IntGetter<T>(value); 
} 

class C 
{ 
public: 
    virtual int getAnInt(IntGetterBase const& v) 
    { 
     return v.getTheInt(); 
    } 
}; 
#endif 

int getint(double d) 
{ 
    return static_cast<int>(d); 
} 

int getint(char const* s) 
{ 
    return strlen(s); 
} 

int main() 
{ 
    C c; 
    std::cout << c.getAnInt(makeIntGetter(3.141)) + c.getAnInt(makeIntGetter("foo")) << '\n'; 
    return 0; 
} 
+1

提及類型擦除+1 – sbi 2009-08-14 19:45:43