[英]Use a forward-declared class in a virtual function in a template baseclass where the constructor only needs the forward declare?
我試圖找出失敗的原因。 我正在使用的代碼基本上壓縮到下面的代碼。 我有一個簡單的A類,我專門用一個模板。 模板不需要這種類型來編譯它的構造函數,並且我實際調用的構造函數(派生類型)沒有公開,因此編譯器此時無法為構造函數生成代碼。
GCC和Clang沒有。 然而,MSVC(2008 + 2010)確實嘗試編譯虛擬成員,因此不進行編譯。
這是錯誤的GCC和Clang,還是來自MSVC? 或者我是否會進入UB領土?
class A;
template <typename X>
class S {
public:
S() {}
virtual int useX() { return X::value; }
};
class T : public S<A> {
public:
T();
};
int main()
{
new T();
return 0;
}
當MSVC實例化一個類時,它還會填充其vtable,並為此目的實例化其所有虛函數,即使是那些從未調用過的函數。
在您的情況下,如果沒有編譯器看到A的完整定義,則無法實例化函數useX
。
如果您將useX聲明為非虛擬,則MSVC可以正常工作。
看來這種行為依賴於編譯器; 例如,AIX在實例化(未使用)函數時比MSVC更具攻擊性。
首先,正如許多人所說,標准完全允許編譯器在這種情況下實例化(或選擇不實例化)他們的內容; 通過嚴格解釋標准,這是代碼問題,而不是MSVC錯誤。 您依賴的行為是特定於平台的,因此是非標准的; 雖然這不是一個“蟲子”本身,但它顯然是不便攜的。
但是,理解為什么GCC和Clang與MSVC有所不同,這與在每個編譯器中如何設置vtable有關。
快速概述對象在內存中的外觀:
在第一種情況下,所有信息都可以是靜態的; 成員函數只是隱藏的正常函數。 在第二種情況下,派生類(或類,對於長繼承列表)可以巧妙地放在內存中的基類之后,以便Derived * == Base *。 顯然,這種優化不適用於多重繼承,這意味着每次調用都需要調整this指針。 然后,虛擬繼承添加一個數組vtable,它在運行時動態選擇正確的函數。
這與未使用的成員的實例化有什么關系?
一般而言,無論您的實施方式如何,您都需要三件事:
G ++有一個標准化且復制良好的方式來進行這些調整,在許多編譯器中使用(包括Clang,因為Clang意味着要取代GCC):
這是一種奇怪的緩存優化,它有一些好處; 它被廣泛復制,盡管不是最優雅的解決方案。 它對所有情況使用一種結構,並且每次執行相同的計算,具體取決於編譯器優化以在適用時用直接調用替換虛擬表跳(GCC優化器非常出色的任務)。
另一方面,MSVC不僅有點不優雅。 這是一個可怕的黑客:它為每個案例使用不同的結構。 這允許它避免不必要的計算的開銷,並且在許多情況下節省空間。 但是,這意味着轉換成員函數指針會導致它們更改大小,在常見情況下(派生到基礎)會導致它們丟失信息 。
因此,與更優雅的GCC實現不同,MSVC絕對必須實例化虛擬成員函數; 如果沒有,它很容易在翻譯單元之間或鏈接過程中丟失信息,並且具有由不同大小的結構表示的相同對象!
這是一個問題,他們實際上添加了關鍵字來處理它: http : //msdn.microsoft.com/en-us/library/ck561bfk.aspx
所以,雖然你不認為編譯器需要這種類型,但MSVC肯定會這樣做,否則它幾乎肯定是不安全的。
編輯:混淆自己簡單的單繼承,基類去了第一:
class A : public B
變
[[B] A]
所以指針* B == * A.
從草案C ++ 1y標准:(通過@DyP)
實現不應隱式實例化函數模板,變量模板,成員模板,非虛擬成員函數,成員類或不需要實例化的類模板的靜態數據成員。 如果虛擬成員函數不會被實現,則實現是否隱式實例化類模板的虛擬成員函數是未指定的。 在默認參數中使用模板特化不應導致模板被隱式實例化,除了可以實例化類模板,其中需要其完整類型來確定默認參數的正確性。 在函數調用中使用默認參數會導致默認參數中的特化被隱式實例化。
強調我的。 任何C ++編譯器都可以在任何時候實例化template
任何virtual
函數,而不會違反C ++標准。 因此,MSVC是符合此事項的標准。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.