【问题标题】:Converting a pointer for a base class into an inherited class将基类的指针转换为继承类
【发布时间】:2011-02-01 05:05:11
【问题描述】:

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

我对这个很困惑。非常感谢任何帮助。

【问题讨论】:

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


    【解决方案1】:

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

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

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

    【讨论】:

    • 目前的做法是简单的原型。目前设计相当混乱,需要以更简洁的方式重新编写。
    【解决方案2】:

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

    // 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 可能是一个强大但有点复杂的工具。

    【讨论】:

    • 不幸的是,访问者模式有其自身的缺点......比如类型的必要枚举:/
    【解决方案3】:

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

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

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

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

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

    还有一个“高级访客”。它知道何时退出。

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

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

    【讨论】:

      【解决方案4】:

      只要施放它:

      XItem* Item = (XItem*)Ent;
      

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

      if (XItem *Item = dynamic_cast<XItem*>(Ent)) {
          Ent->RemoveEntity();
      
          // Code to link into inventory is here
      
          break;
      }
      

      【讨论】:

      • +1 因为您回答了提出的问题 并且 表明了这样做的更好方法。 C 风格的铸造运营商可能是“邪恶的”,但不应禁止了解它们。
      • 没有投反对票,但仍不应以通常不鼓励的答案为首
      【解决方案5】:
      XItem * Item = dynamic_cast< XItem * >( Ent );
      
      if ( Item )
          // do something with item
      

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

      【讨论】:

      • 然后在使用之前检查 Item 不为零。以防万一;-)
      【解决方案6】:

      如已回答,有 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
      }
      

      这样我可以在开发过程中进行类型检查(如果出现问题则例外),并在完成后加快速度。如果您愿意,您可以使用其他策略,但我必须承认我非常喜欢这种方式:)

      【讨论】:

        猜你喜欢
        • 2011-02-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-09-23
        • 1970-01-01
        • 1970-01-01
        • 2015-08-12
        • 2011-04-22
        相关资源
        最近更新 更多