我应该改变我的设计以防止动态演员表吗?

时间:2013-03-21 15:12:40

标签: c++ oop dynamic-cast

我已经阅读了几个关于C ++动态转换的线程,所有人都声称它表明设计不好。在其他语言中,我在检查对象的类型时从未考虑过太多。我从不使用它作为多态性的替代,只有当强耦合看起来完全可以接受时。我经常遇到的这些情况之一:有一个列表(我在C ++中使用std :: vector)的对象,都是从一个公共基类派生的。该列表由一个允许知道不同子类的对象管理(通常它是管理对象类中私有类的一个小层次)。通过将它们保存在单个列表(数组,向量,...)中,我仍然可以从多态中受益,但是当一个操作意图作用于特定子类的对象时,我使用动态强制转换或类似的东西。

对于这种类型的问题,如果没有动态强制转换或我缺少的类型检查,是否有不同的方法?我真的好奇那些不惜一切代价避免这些的程序员会如何处理它们。

如果我的描述过于抽象,我可以在C ++中编写一个简单的例子(编辑:见下文)。

class EntityContacts {
private:
  class EntityContact {
  private:
    virtual void someVirtualFunction() { };            // Only there to make dynamic_cast work
  public:
      b2Contact* m_contactData;
  };

  class InternalEntityContact : public EntityContact {
  public:
    InternalEntityContact(b2Fixture* fixture1, b2Fixture* fixture2){
        m_internalFixture1 = fixture1;
        m_internalFixture2 = fixture2;
    };

    b2Fixture* m_internalFixture1;
    b2Fixture* m_internalFixture2;
  };

  class ExternalEntityContact : public EntityContact {
  public:
    ExternalEntityContact(b2Fixture* internalFixture, b2Fixture* externalFixture){
        m_internalFixture = internalFixture;
        m_externalFixture = externalFixture;
    };

    b2Fixture* m_internalFixture;
    b2Fixture* m_externalFixture;
  };

  PhysicsEntity* m_entity;
  std::vector<EntityContact*> m_contacts;
public:
  EntityContacts(PhysicsEntity* entity)
  {
    m_entity = entity;
  }

  void addContact(b2Contact* contactData)
  {
    // Create object for internal or external contact
    EntityContact* newContact;
    if (m_entity->isExternalContact(contactData)) {
        b2Fixture* iFixture;
        b2Fixture* eFixture;
        m_entity->getContactInExFixtures(contactData, iFixture, eFixture);
        newContact = new ExternalEntityContact(iFixture, eFixture);
    }
    else
        newContact = new InternalEntityContact(contactData->GetFixtureA(), contactData->GetFixtureB());

    // Add object to vector
    m_contacts.push_back(newContact);
  };

  int getExternalEntityContactCount(PhysicsEntity* entity)
  {
    // Return number of external contacts with the entity
    int result = 0;
    for (int i = 0; i < m_contacts.size(); ++i) {
        ExternalEntityContact* externalContact = dynamic_cast<ExternalEntityContact*>(m_contacts[i]);
        if (externalContact != NULL && getFixtureEntity(externalContact->m_externalFixture) == entity)
            result++;
    }
    return result;
  }
};

这是我在使用box2d物理的游戏中用于碰撞检测的类的简化版本。我希望box2d的细节不会分散我想要展示的内容。我有一个非常相似的类'Event',它创建不同类型的事件处理程序,它们以相同的方式构造(使用基类EventHandler的子类而不是EntityContact)。

6 个答案:

答案 0 :(得分:8)

至少从我的角度来看,dynamic_cast存在是有原因的,有时候使用它是合理的。这可能是其中一次。

根据您描述的情况,一个可能的替代方案可能是在基类中定义您需要的更多操作,但是将它们定义为(可能是静默地)失败如果为基类或其他不支持这些操作的类调用它们。

真正的问题是以这种方式定义您的操作是否有意义。回到典型的基于动物的层次结构,如果你正在与鸟类合作,那么Bird类定义一个fly成员通常是明智的,对于少数无法飞行的鸟类来说,只是让它失败了(理论上应该将其重命名为attempt_to_fly,但这很少能实现。)

如果你看到了很多这样的内容,那么它往往表明你的课程缺乏抽象 - 例如,你可能真的想要一个flyattempt_to_fly而不是travel或{{1}}。 {1}}成员,由个体动物决定是否通过游泳,爬行,行走,飞行等来做到这一点。

答案 1 :(得分:0)

  

但是当一个操作意图作用于特定的对象时   子类我使用动态转换或类似的东西

听起来像对象建模不正确。你有一个包含子类实例的列表,但它们实际上并不是子类,因为你不能以同样的方式对它们进行操作(Liskov等)。

一种可能的解决方案是扩展基类,使得您有一组某些子类可以覆盖的无操作方法。但这听起来仍然不太正确。

答案 2 :(得分:0)

问题非常广泛,因此需要考虑一些一般性问题:

  • 这是语言的一个特征,原因是有正确的用例。
  • 一般来说,这本身并不好或坏。
  • 检查您的问题是否是正确的解决方案。
  • 了解替代方案以解决您的问题。
  • 将其与其他语言进行比较,应该包括C ++ / C ++ 11的替代品是否可以用其他语言编写的问题。
  • dynamic_cast会产生一定的成本。如果是关于运行时性能,请根据备选方案的成本进行检查。
  • 检查是在运行时执行的,如果未正确测试,则会产生一定风险,即您提供错误的软件。通过静态类型检查,编译器可以帮助您,甚至保证,不会发生某些问题/错误。

答案 3 :(得分:0)

通常在这种情况下,您不会管理派生对象列表,而是管理引用实际派生对象的接口指针(或基本指针)列表。只要您想对特定对象执行操作,就可以通过其接口/基类访问它,该接口/基类应该公开所有必需的功能。派生类应该通过特定的实现覆盖公开的基本功能。

答案 4 :(得分:0)

使用dynamic_cast是想要实现像Visitor或Command这样的设计模式的症状,所以我建议重构以使其显而易见

答案 5 :(得分:0)

与任何被认为是错误编程的方法一样,总会有一些例外。当一个程序员说“这样的事情是邪恶的而你永远不应该这样做”时,他们的确意味着“几乎没有理由使用这样的等等,所以你最好能够自己解释一下。”我,我自己,从来没有遇到dynamic_cast是绝对必要且无法轻易重构的情况。但是,如果它完成了工作,那就这样吧。

由于我们不知道您的具体问题,我只能根据您告诉我们的情况给出建议。你说有一个多态基类型的容器。例如:

std::vector<Base*> bases;

如果您要使用以特定于派生的方式放置到此容器中的任何对象,那么它实际上不是基础对象的容器吗?拥有指向Base个对象的容器的重点是,您可以迭代它们并将它们全部视为Base个对象。如果您必须dynamic_cast到某种Derived类型,那么您就滥用了Base* s的多态行为。

如果Derived继承自Base,则Derived 是-a Base。简而言之,如果您使用指向Base类型的多态Derived指针,那么您应该只使用Derived的特征使其成为Base