2010-03-26 50 views
3

我正在研究一個小型的roguelike遊戲,並且任何不屬於該地圖一部分的對象/「事物」都基於XEntity類。有幾個類依賴於它,例如XPlayer,XItem和XMonster。將基類的指針轉換爲繼承類

我的問題是,當我知道一個對象在項目中時,我想將指針從XEntity轉換爲XItem。我用來拾取物品的示例代碼是這樣的,當不同的實體拾取它正在站立的物品時。

void XEntity::PickupItem() 
{ 
    XEntity *Ent = MapList; // Start of a linked list 

    while(true) 
    { 
     if(Ent == NULL) { break; } 

     if(Ent->Flags & ENT_ITEM) 
     { 
      Ent->RemoveEntity(); // Unlink from the map's linked list 

      XItem *Item = Ent // Problem is here, type-safety 

      // Code to link into inventory is here 

      break; 
     } 

     Ent = Ent->MapList; 
    } 
} 

我首先想到的是建立在XEntity的方法,其本身返回作爲XItem指針,但它創建循環依賴是無法解決的。

我對這個很難過。任何幫助是極大的讚賞。

回答

7

如果您知道XEntity是實際工作和XItem那麼你可以使用一個靜態的投。

XItem* Item = static_cast<XItem *>(Ent); 

但是,你應該檢查你的設計,看看你是否可以在某種程度上實體運營這意味着,你不需要知道派生類型是。如果您可以爲基類提供足夠豐富的接口,則可以消除標誌檢查類型檢查。

+0

目前的做法是爲了一個簡單的原型。目前設計非常混亂,需要以更清晰的方式重新編寫。 – 2010-03-26 20:26:18

1
XItem * Item = dynamic_cast< XItem * >(Ent); 

if (Item) 
    // do something with item 

爲了使它起作用,您需要啓用RTTI。看here欲瞭解更多信息。

+1

然後在使用它之前檢查Item是否爲零。以防萬一;-) – 2010-03-26 14:34:50

1

只投它:

XItem* Item = (XItem*)Ent; 

一個更好的辦法,總體來說,是這樣的:

if (XItem *Item = dynamic_cast<XItem*>(Ent)) { 
    Ent->RemoveEntity(); 

    // Code to link into inventory is here 

    break; 
} 
+0

-1,因爲應該使用C++轉換運算符。 – 2010-03-26 14:35:56

+0

+1,因爲你回答了問題*和*表示這樣做的更好方法。 C型鑄造操作員可能是「邪惡」的,但不應該禁止他們的知識。 – 2010-03-26 15:00:19

+0

沒有downvote,但仍然不應該帶着一般不鼓勵的答案 – 2010-03-26 17:34:35

3

鑄造解決了這個問題,正如其他人指出:

// dynamic_cast validates that the cast is possible. It requires RTTI 
// (runtime type identification) to work. It will return NULL if the 
// cast is not possible. 
XItem* Item = dynamic_cast<XItem*>(Ent); 
if(Item) 
{ 
    // Do whatever you want with the Item. 
} 
else 
{ 
    // Possibly error handling code as Ent is not an Item. 
} 

不過,我認爲你前人的精力退一步,看看在程序的設計,向下轉換是應該並且可以通過被避免的適當的面向對象設計。一個強大的,即使有點複雜的工具可能是Visitor pattern

+0

不幸的是訪客模式有它自己的缺點......就像類型的必要枚舉:/ – 2010-03-26 16:42:55

2

我曾經相信,向下轉換總是可以通過「適當」的設計來避免。但情況並非如此。正確的設計通常需要具有實現新功能的子對象,而不僅僅是不同的行爲。很多時候,倡導「適當」設計的人會告訴你將新的行爲向抽象堆棧移動到不屬於它的地方。並不總是如此,但如果你一直試圖確保你的所有類都可以從最抽象的角度來使用,那麼這往往是事情最終發生的地方,而且它只是非常糟糕。

以集中方式處理向下轉換的一個好方法是使用訪問者模式。雖然有幾種形式的訪問者,但有些需要向下轉發,有些則不需要。非循環訪問者,確實需要向下轉換,更容易使用,而且根據我的經驗,它更加強大。

我還沒有嘗試過的另一位訪問者聲稱以標準訪問者的速度滿足非循環訪問者的相同靈活性;它被稱爲「合作伙伴」。它仍然投射,它只是通過它自己的查找表以更快的速度完成。我之所以沒有嘗試合作伙伴的原因是因爲我沒有找到一種方法讓它在多個高層次上工作......但是我沒有花太多時間在這個問題上,因爲我一直堅持自己(在我的當前項目)與非循環。

關於合作伙伴的真正的很酷的事情是返回類型。但是,我使用我的訪問者訪問整個對象塊並與他們做事。我很難想象如何在這些情況下返回工作。

標準訪問者downcasts也只是通過虛擬調用機制,這是比明確演員更快,有時更安全。我不喜歡這個訪問者的事情是,如果你需要訪問Widget高層的WidgetX,那麼即使你不關心它們,你也必須爲WidgetY和WidgetZ實現visit()功能。隨着大型和/或廣泛的高層次,這可以是PITA。其他選項不需要這個。

還有一個「higherarchal訪問者」。它知道什麼時候退出。

如果你不傾向於使用訪問者,但希望只投射,那麼你可以考慮使用boost :: polymorphic_downcast函數。它具有在調試版本中動態強制轉換的安全和警告機制,以及靜態轉換在發行版中的速度。可能沒有必要。有時候你只知道你演對了。

您需要考慮的重要事項以及您想要避免的是打破LSP。如果你有大量的代碼「if(widget-> type()== type1){downcast ...} else if(widget-> type()== type2)...」,那麼添加新的widget類型是一個很大的問題,它會以不好的方式影響很多代碼。你的新小部件不會真的成爲一個小部件,因爲你的所有客戶都與你的高層親密,並且不知道它。訪問者模式並沒有擺脫這個問題,但它確實集中了,當你聞到一股難聞的氣味時,這非常重要,而且它通常會使它更容易處理。

1

由於已經回答了,有2個運營商:

XItem* Item = static_cast<XItem*>(Ent); 

和:

XItem* Item = dynamic_cast<XItem*>(Ent); 

二是速度慢,但更安全(它檢查是否有可能),並可能返回null即使Ent不是。

我傾向於使用這兩種包裹在一個方法:

template <class T, class U> 
T* my_cast(U* item) 
{ 
#ifdef _NDEBUG_ 
    if (item) return &dynamic_cast<T&>(*item); // throw std::bad_cast 
    else return 0; 
#else 
    return static_cast<T*>(item); 
#endif 
} 

這樣,我得到的類型檢查,同時發展(有一個例外,如果出現壞的),我得到的速度時,我完蛋了。如果您願意,您可以使用其他策略,但我必須承認我非常喜歡這種方式:)

相關問題