2013-08-03 34 views
0

我有下面的類層次結構:爲什麼C風格的演員表現不同於dynamic_cast?

class IControl 
{ 
    virtual void SomeMethod() = 0; // Just to make IControl polymorphic. 
}; 

class ControlBase 
{ 
public: 
    virtual int GetType() = 0; 
}; 

class ControlImpl : public ControlBase, public IControl 
{ 
public: 
    virtual void SomeMethod() { } 

    virtual int GetType() 
    { 
     return 1; 
    } 
}; 

我有一個ICONTROL抽象類和ControlBase類。 ControlBase類不會從IControl繼承,但我知道每個IControl實現都將從ControlBase派生。

我有下面的測試代碼中,我與施放ICONTROL引用到ControlBase(因爲我知道它提煉出來的)的dynamic_cast,也與C風格投:

int main() 
{ 
    ControlImpl stb; 
    IControl& control = stb; 

    ControlBase& testCB1 = dynamic_cast<ControlBase&>(control); 
    ControlBase& testCB2 = (ControlBase&)control; 
    ControlBase* testCB3 = (ControlBase*)&control; 

    std::cout << &testCB1 << std::endl; 
    std::cout << &testCB2 << std::endl; 
    std::cout << testCB3 << std::endl; 
    std::cout << std::endl; 
    std::cout << testCB1.GetType() << std::endl; // This properly prints "1". 
    std::cout << testCB2.GetType() << std::endl; // This prints some random number. 
    std::cout << testCB3->GetType() << std::endl; // This prints some random number. 
} 

只有dynamic_cast能夠正常工作,另外兩個強制轉回的內存地址略有不同,GetType()函數會返回不正確的值。

這是什麼原因? C風格演員是否最終使用reinterpret_cast?它與多態對象如何在內存中對齊有關?

+2

可能重複[何時應使用靜態\ _cast,動態\ _cast和reinterpret \ _cast?](http://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-and -reinterpre-cast-be-used) –

+2

我已經看到了這個問題並閱讀了答案(以及關於此主題的其他答案)。但對我而言,這仍然不清楚爲什麼在這個具體情景中會發生這種情況,這就是爲什麼我發佈了一個單獨的問題。 –

+1

請注意'ControlBase * testCB3 = static_cast (&control);'和'ControlBase * testCB4 = static_cast (&control);'之間存在差異。後者不調用UB(並且爲'GetType()'測試正確地生成'1')。 – dyp

回答

7

我認爲你的例子中的類名有點混亂。我們稱它們爲Interface,BaseImpl。請注意0​​和Base無關

C++ Standard在[expr.cast]中定義了C風格轉換,稱爲「顯式類型轉換(轉換符號)」。你可以(也許應該)閱讀整個段落,以確切地知道C風格演員是如何定義的。在OP的例子中,以下是足夠的:

A C風格可以執行的[expr.cast]一個/ 4的轉化率:

  • const_cast
  • static_cast
  • static_cast其次const_cast
  • reinterpret_cast
  • reinterpret_cast其次const_cast

這個列表的順序是非常重要的,因爲:

如果轉換一個以上的上面所列的方式,首先出現在列表中使用的解釋來解釋,甚至如果由該解釋產生的演員陣容不合格。

讓我們來看看你的榜樣

Impl impl; 
Interface* pIntfc = &impl; 
Base* pBase = (Base*)pIntfc; 

不能使用的const_cast,在列表中的下一個元素是static_cast。但是類InterfaceBase無關,因此沒有static_cast,可以轉換從Interface*Base*。因此,使用reinterpret_cast

附加說明:實際的回答你的問題是:因爲有上面的列表中沒有dynamic_cast,C風格的投從來沒有表現得像一個dynamic_cast


如何實際地址改變不是C++語言定義的一部分,但我們可以利用它可以如何實現的例子:

與至少一個虛擬類的每個對象函數(繼承或擁有)包含(在此示例中,讀取:可能包含)指向vtable的指針。如果它從多個類繼承虛擬函數,則它包含多個指向vtables的指針。由於空基類優化(沒有數據成員)的Impl一個實例可以是這樣的:

 
+=Impl=======================================+ 
|           | 
| +-Base---------+ +-Interface---------+ | 
| | vtable_Base* | | vtable_Interface* | | 
| +--------------+ +-------------------+ | 
|           | 
+============================================+ 

現在,例如:

 Impl impl; 

    Impl* pImpl = &impl; 
Interface* pIntfc = pImpl; 
    Base* pBase = pImpl; 
 
+=Impl=======================================+ 
|           | 
| +-Base---------+ +-Interface---------+ | 
| | vtable_Base* | | vtable_Interface* | | 
| +--------------+ +-------------------+ | 
|^    ^     | 
+==|==================|======================+ 
^ |     | 
| +-- pBase   +-- pIntfc 
| 
+-- pimpl 

如果你不是做一個reinterpret_cast,結果是實現定義的,但可能會導致如下所示:

 Impl impl; 

    Impl* pImpl = &impl; 
Interface* pIntfc = pImpl; 
    Base* pBase = reinterpret_cast<Base*>(pIntfc); 
 
+=Impl=======================================+ 
|           | 
| +-Base---------+ +-Interface---------+ | 
| | vtable_Base* | | vtable_Interface* | | 
| +--------------+ +-------------------+ | 
|     ^     | 
+=====================|======================+ 
^      | 
|      +-- pIntfc 
|      | 
+-- pimpl    +-- pBase 

即,地址不變,pBase指向Impl對象的Interface子對象。

請注意,取消引用指針pBase將我們帶到UB-land,標準並未指定應發生什麼。在這個示例性實現中,如果調用pBase->GetType(),則使用vtable_Interface*,其中包含SomeMethod條目,並調用該函數。這個功能不會返回任何東西,所以在這個例子中是,鼻魔被召喚並接管了世界。或者從堆棧中取出一些值作爲返回值。

+0

感謝您的徹底解答,這很有道理!在這些行中:'Interface * pIntfc = pImpl;'或'Base * pBase = pImpl;',我們甚至不必使用dynamic_cast,因爲我們向上施加了繼承樹,這可以隱式完成,對嗎? –

+0

右:[conv.ptr]/3「指向* cv *'D'」的指針類型的值,其中'D'是一個類的類型,可以轉換爲類型爲「指針 」的前值爲* cv * 'B'「,其中'B'是'D'的基類,如果'B'是'D'的不可訪問或不明確的基類,則需要這種轉換的程序是不合格的。 – dyp

3

這是什麼原因?

確切的原因是,dynamic_cast保證在這種情況下按標準工作,而其他類型調用未定義的行爲。

C風格演員是否最終使用reinterpret_cast?

是的,在這種情況下它的確如此。 (A方面說明:永不使用C風格演員)。

它與多態對象如何在內存中對齊有關嗎?

我會說它與使用多重繼承的多態對象在內存中的佈局方式有關。在具有單一繼承的語言中,dynamic_cast將不是必需的,因爲基礎子對象地址將與派生對象地址重合。在多重繼承情況下,情況並非如此,因爲存在多個基本子對象,並且不同基本子對象必須具有不同的地址。

有時編譯器可以計算每個子對象地址與派生對象地址之間的偏移量。如果偏移量不爲零,則投射操作將成爲指針加法或減法而不是空操作。 (在虛擬繼承upcast的情況下,它有點複雜,但編譯器仍然可以這樣做)。

有至少兩個情況下,當編譯器不能做到這一點:

  1. 交叉鑄造(即,兩個類均未是一個基類中的另一個之間)。
  2. 從虛擬基地向下傾斜。

在這些情況下,dynamic_cast是鑄造的唯一方法。

相關問題