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 指针返回的方法,但它会创建无法解析的循环依赖关系。

我对这个很困惑。任何帮助是极大的赞赏。

4

6 回答 6

7

如果你知道XEntity是实际的,XItem那么你可以使用静态转换。

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

但是,您应该检查您的设计,看看您是否可以对实体进行操作,这意味着您不需要知道它是什么派生类型。如果您可以为基类提供足够丰富的接口,您也许可以消除标志检查类型检查。

于 2010-03-26T14:38:01.853 回答
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.
}

但是我认为你应该退后一步看看程序的设计,因为向下转换是应该并且可以通过适当的面向对象设计来避免的事情。访问者模式可能是一个强大但有点复杂的工具。

于 2010-03-26T14:40:36.250 回答
2

我曾经相信通过“适当的”设计总是可以避免向下转换。但事实并非如此。一个适当的设计通常需要具有实现新行为的子对象,而不仅仅是不同的行为。“正确”设计的倡导者经常会告诉您将新行为移到抽象堆栈中不属于它的地方。并非总是如此,但是如果您继续尝试确保可以从最抽象的角度使用您的所有类,那么这通常是事情最终走向的地方,而且很糟糕。

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

另一个我没有尝试过的访问者声称以标准访问者的速度满足非循环访问者的相同灵活性;它被称为“合作访客”。它仍然投射,只是使用自己的查找表以更快的方式投射。我没有尝试过合作访问者的原因是我还没有找到一种方法让它在多个更高层次上工作......但我也没有花很多时间在这上面,因为我已经坚持自己(在我的当前项目)与非循环。

合作访问者真正酷的地方在于返回类型。然而,我使用我的访问者来访问整个对象块并用它们做事。我很难想象在这些情况下退货将如何运作。

标准访问者向下转换也只是通过虚拟调用机制来实现,这比显式转换更快,有时更安全。我不喜欢这个访问者的一点是,如果您需要在 Widget 更高层次中访问 WidgetX,那么即使您不关心它们,您也必须为 WidgetY 和 WidgetZ 实现 visit() 功能。对于大型和/或广泛的更高层次,这可以是 PITA。其他选项不需要这个。

还有一个“上位客”。它知道何时退出。

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

您需要考虑和要避免的重要事情是破坏 LSP。如果你有一大堆代码“if (widget->type() == type1) { downcast...} else if (widget->type() == type2)...”然后添加新的小部件类型是一个大问题,它以不好的方式影响了很多代码。您的新小部件不会真正成为小部件,因为您的所有客户都与您的高层关系过于亲密并且不知道它。访问者模式并没有摆脱这个问题,但它确实集中了,当你闻到难闻的气味时,这非常重要,而且它通常使处理它变得更简单。

于 2010-03-26T16:09:04.253 回答
1
XItem * Item = dynamic_cast< XItem * >( Ent );

if ( Item )
    // do something with item

为了使其工作,您需要启用 RTTI。在这里查看更多信息。

于 2010-03-26T14:33:18.790 回答
1

只需投射它:

XItem* Item = (XItem*)Ent;

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

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

    // Code to link into inventory is here

    break;
}
于 2010-03-26T14:34:37.133 回答
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
}

This way I get type checking while development (with an exception if something goes bad) and I get speed when I'm finished. You can use other strategies if you wish, but I must admit I quite like this way :)

于 2010-03-26T16:48:25.570 回答