【问题标题】:Should I change my design to prevent dynamic casts?我应该改变我的设计以防止动态转换吗?
【发布时间】:2013-03-21 15:12:39
【问题描述】:

我已经阅读了几个关于 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)。

【问题讨论】:

  • 在不知道您使用哪些其他语言的情况下,请注意,例如 Java 中的 instanceof 同样不受欢迎。有合法用途,但通常不遵循 OO 概念。从 OO 的角度来看,人们可能会问为什么管理器对象需要了解子类,是否应该抽象对特定子类的操作等等。
  • dynamic_cast 对我来说就像一个设计捷径。一切都可以从一个公共基类(class Base {};)派生并存储在一个列表中(std::list&lt;Base&gt; l)。但是当我们稍后使用dynamic_cast 时,它会导致几个问题:我们应该使用继承吗?还是我们已经足够抽象了? - 我们还有其他可用的工具(模板和界面)

标签: c++ oop dynamic-cast


【解决方案1】:

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

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

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

如果您看到很多这种情况,这往往表明您的类中缺乏抽象——例如,您可能真的想要一个travel 成员,而不是flyattempt_to_fly,由个体动物决定是否通过游泳、爬行、步行、飞行等方式来做到这一点。

【讨论】:

    【解决方案2】:

    这个

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

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

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

    【讨论】:

      【解决方案3】:

      这个问题很宽泛,所以需要考虑一些一般性要点:

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

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          【解决方案5】:

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

          【讨论】:

            【解决方案6】:

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

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

            std::vector<Base*> bases;
            

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

            如果Derived 继承自Base,则Derived is-a Base。简单地说,如果你使用多态 Base 指针指向 Derived 类型,那么你应该只使用 Derived 使其成为 Base 的特性。

            【讨论】:

              猜你喜欢
              • 2019-10-28
              • 1970-01-01
              • 1970-01-01
              • 2015-12-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-12-20
              • 1970-01-01
              相关资源
              最近更新 更多