2010-03-26 59 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 
} 

这样,我得到的类型检查,同时发展(有一个例外,如果出现坏的),我得到的速度时,我完蛋了。如果您愿意,您可以使用其他策略,但我必须承认我非常喜欢这种方式:)