将基类的指针转换为继承的类

时间:2010-03-26 14:31:01

标签: c++ pointers class oop type-safety

我正在开发一个小型的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指针,但它会创建无法解析的循环依赖项。

我对这个很难过。非常感谢任何帮助。

6 个答案:

答案 0 :(得分:7)

如果您知道 XEntity是真的并且XItem那么您可以使用静态演员。

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

但是,您应该检查您的设计并查看您是否可以以某种方式对该实体进行操作,这意味着您不需要知道它是什么派生类型。如果您可以为基类提供足够丰富的接口,则可以省略标记检查类型检查。

答案 1 :(得分:3)

Casting解决了其他人指出的问题:

// 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

答案 2 :(得分:2)

我曾经认为,通过“适当”的设计总是可以避免向下倾斜。但事实并非如此。正确的设计通常需要具有实现新的,而不仅仅是不同行为的子对象。过于频繁地提倡“正确”设计会告诉您将新行为从抽象堆栈移到不属于它的地方。并非总是如此,但如果你一直在努力确保所有的课程都可以从最抽象的角度使用,那么这往往是事情最终会发生的事情,而且只是很难看。

以集中方式处理向下转换的一个好方法是使用访问者模式。虽然有几种形式的访问者,有些需要向下转发,有些则不需要。非循环访问者,确实需要向下转换,更容易使用,并且根据我的经验,更强大。

我没有尝试过的另一位访客声称以标准访客的速度满足非循环访客的相同灵活性;它被称为“合作访客”。它仍然可以使用它自己的查找表以更快的方式进行转换。我没有尝试合作访问者的原因是我没有找到一种方法让它在多个高层建筑上工作......但是我没有花很多时间在它上面因为我已经陷入困境(在我的当前项目)与非循环。

合作访问者真正酷的是返回类型。但是,我使用访问者访问整个对象块并使用它们进行操作。我很难想象在这些情况下返回是如何工作的。

标准的访客向下倾斜也是通过虚拟呼叫机制实现的,这种机制比明确的演员更快,有时更安全。我不喜欢这个访问者的事情是,如果你需要访问Widget高层次的WidgetX,那么即使你不关心它们,你也必须为WidgetY和WidgetZ实现visit()功能。对于大型和/或广泛的高层,这可以是PITA。其他选项不需要这个。

还有一个“高层访客”。它知道什么时候退出。

如果您不倾向于使用访问者并且希望只是强制转换,那么您可以考虑使用boost :: polymorphic_downcast函数。它具有动态转换的安全和警告机制,在调试版本中具有断言,以及在发布中静态转换的速度。但可能没有必要。有时候你只知道自己是正确的。

您需要考虑的重要事项以及您想要避免的是打破LSP。如果您有大量代码使用“if(widget-&gt; type()== type1){downcast ...} else if(widget-&gt; type()== type2)...”然后添加新代码窗口小部件类型是一个很大的问题,会以一种糟糕的方式影响很多代码。你的新小部件不会真的是一个小部件,因为你的所有客户都与你的高层次太亲密而且不知道它。访问者模式并没有摆脱这个问题,但它确实集中在一起,这在你闻到难闻的气味时非常重要,并且它常常使得处理它更加简单。

答案 3 :(得分:1)

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

if ( Item )
    // do something with item

为了使其正常工作,您需要启用RTTI。查看here了解更多信息。

答案 4 :(得分:1)

投下它:

XItem* Item = (XItem*)Ent;

总体而言,更好的方法是:

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

    // Code to link into inventory is here

    break;
}

答案 5 :(得分:1)

如前所述,有2个运营商:

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

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

第二个更慢但更安全(它检查是否可能)并且即使Ent不是,也可能返回null。

我倾向于使用两种方法包装:

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
}

这样我在开发时会进行类型检查(如果出现问题会有例外),当我完成时我会获得速度。如果你愿意,你可以使用其他策略,但我必须承认我非常喜欢这种方式:)