0

我試圖創建從作爲接着多個類繼承的類,得到一個「菱形」
(d選自B繼承和C. B和C從A 幾乎都繼承):
虛擬多重繼承和鑄造

  A
 /\
B   C
  \ /
  D

現在,我有保存指向基類(一個)鏈表的容器。
當我嘗試顯式地轉換爲指針時(typeid檢查後),我得到以下錯誤:
「無法將指向基類」A「的指針轉換爲派生類」D「的指針 - 基類是虛擬的」

但是,當我使用動態鑄造它似乎工作得很好。
任何人都可以請向我解釋爲什麼我必須使用動態轉換,爲什麼虛擬繼承會導致此錯誤?

+6

考慮修復您的設計。 :) – rightfold

+0

請問您可以添加一些代碼嗎? – dkrikun

+0

什麼類型的轉換無法工作(靜態,重新解釋或C風格)? –

回答

2

「虛擬」總是意味着「在運行時確定」。虛擬功能位於運行時,虛擬基地也位於運行時。虛擬性的全部觀點是,所討論的實際目標不是靜態可知的。

因此,無法確定在編譯時您獲得虛擬基礎的派生最多的對象,因爲基礎與派生最多的對象之間的關係不固定。你必須等到你知道什麼實際的目標是之前,你可以決定它是相對於基地。這就是動態演員所做的。

+0

但是爲什麼在確定了對象的類型後,我不能「強制」顯式強制轉換? typeid檢查和動態轉換之後的顯式轉換有什麼區別? @Kerrek SB –

+0

@DavidTzoor看看這裏:http://stackoverflow.com/questions/15921372/c-virtual-table-layout-of-mimultiple-inheritance – Johan

+2

@DavidTzoor:再次,你不知道*結構,不管你知道多少種類型。想象一下,你想在你的例子中將一個'A *'轉換爲'B *'。但要做到這一點,你必須知道對象實際上是'B'還是'D'!每種情況下的答案都是不同的。虛擬基地不屬於「B」,它屬於派生最多的對象。 –

0

When I tried doing explicit casting to a pointer (after typeid check)

成功typeid(x) == typeid(T)後,你知道動態類型的x,你可以在理論上避免任何其他運行在該點檢查涉及dynamic_cast。 OTOH,編譯器不需要做這種靜態分析。

static_cast<T&>(x)不傳達給編譯器的知識,動態類型的x真的是T:前提是較弱(即一個T對象具有x作爲子對象的基類)。

C++可以提供一個static_exact_cast<T&>(x)這是唯一有效的,如果x指定動態型T(而不是一些類型從T衍生,不像static_cast<T&>)的一個對象。這個假設static_exact_cast<T&>(x),假設動態類型的xT,將跳過任何運行時檢查和計算從T對象佈局知識的正確地址:因爲在

D d; 
B &br = d; 

沒有運行時的偏移計算是必要的,static_exact_cast<D&>(br)的反向調整將不涉及運行時偏移計算。

鑑於

B &D_to_B (D &dr) { 
    return dr; 
} 

運行時,需要在D_to_B偏移計算(除非整個程序分析表明,沒有來自D派生類有不同的基類A的偏移量);給定

struct E1 : virtual A 
struct E2 : virtual A 
struct F : E1, D, E2 

D子對象的F的佈局將來自D完整對象的佈局不同:A子對象將是在一個不同的偏移量。 D_to_B所需的偏移量將由D的vtable給出(或直接存儲在對象中);這意味着D_to_B將不僅僅包含一個簡單的「靜態」upcast(在進入對象的構造函數之前,vptr沒有設置,所以這樣的轉換無法工作;在構造函數init列表中注意向上轉換)時,會有一個常量偏移量。

而且BTW,D_to_B (d)static_cast<B&> (d)並沒有什麼不同,所以你可以看到偏移量的運行時間計算可以在static_cast內完成。

考慮下面的代碼編譯天真(假設表示ar具有動態型F沒有華麗的分析):

F f; 
D &dr = f; // static offset 
A &ar = dr; // runtime offset 
D &dr2 = dynamic_cast<D&>(ar); 

查找A基類對象從參考到D(未知動態型的左值)需要對vtable(或等價物)進行運行時檢查。讓我們再回到D子對象需要非平凡計算:

  • 找出完整對象的地址(這恰好是類型F的),使用的A
  • 找出的地址的虛表的f明確並公開派生D基類的子對象,使用虛函數表再次

這是不平凡的作爲dynamic_cast<D&>(ar)靜態知道F沒有具體(的佈局,F的vtable的佈局);一切都從vtable中提取。所有dynamic_cast知道是有一個派生類A和vtable有所有的信息。

當然,C++中沒有static_exact_cast<>,因此您必須使用dynamic_cast以及相關的運行時檢查; dynamic_cast是一個複雜的函數,但複雜度涵蓋了基類的情況;當動態類型被賦予dynamic_cast時,避免了基類樹行走,並且測試非常簡單。

結論:

要麼你名的動態類型dynamic_cast<T>dynamic_cast是快速,簡單,無論如何,還是沒有這樣做,在真正需要的dynamic_cast複雜性。