8

我已经阅读了几个关于 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 的细节不会过多地分散我想要展示的内容。我有一个非常相似的类“事件”,它创建不同类型的事件处理程序,其结构相同(使用基类 EventHandler 的子类而不是 EntityContact)。

4

6 回答 6

8

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

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

真正的问题是,以这种方式定义您的操作是否有意义。回到典型的基于动物的层次结构,如果你在使用 Birds,Bird 类定义一个fly成员通常是明智的,而对于少数不会飞的鸟,让它失败(理论上应该重命名)像这样的东西attempt_to_fly,但这很少能完成很多事情)。

如果你看到很多这样的东西,它往往表明你的类中缺乏抽象——例如,你可能真的想要一个成员,而不是一个flyor ,这取决于个体动物来决定是否这样做通过游泳、爬行、步行、飞行等。attempt_to_flytravel

于 2013-03-21T15:22:25.663 回答
0

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

  • 这是该语言的一个特性是有原因的,这意味着有有效的用例。
  • 一般来说,它本身没有好坏之分。
  • 检查它是否适合您的问题。
  • 了解解决问题的替代方案。
  • 将它与其他语言进行比较应该包括 C++/C++11 的替代方案是否也可以在其他语言中使用的问题。
  • dynamic_cast产生一定的成本。如果它与运行时性能有关,请对照替代方案的成本进行检查。
  • 检查是在运行时执行的,如果没有正确测试,这会产生一定的风险,即您交付有缺陷的软件。通过静态类型检查,编译器可以帮助您,甚至保证不会发生某些问题/错误。
于 2013-03-21T15:19:06.223 回答
0

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

于 2013-03-21T15:20:15.200 回答
0

使用 dynamic_cast 是想要实现像访问者或命令这样的设计模式的症状,所以我建议重构以使其明显

于 2013-03-21T15:23:08.753 回答
0

这个

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

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

一种可能的解决方案是扩展您的基类,以便您拥有一组特定子类可以覆盖的无操作方法。但这听起来仍然不太对劲。

于 2013-03-21T15:16:38.993 回答
0

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

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

std::vector<Base*> bases;

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

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

于 2013-03-21T15:25:19.580 回答