8

C++ の動的キャストに関するいくつかのスレッドを読みましたが、すべてが悪い設計を示していると主張する人でいっぱいです。他の言語では、オブジェクトの型をチェックするときにあまり考えたことはありませんでした。ポリモーフィズムの代わりとして使用することはなく、強い結合が完全に受け入れられると思われる場合にのみ使用します。私が頻繁に遭遇するこれらの状況の 1 つは、すべて共通の基本クラスから派生したオブジェクトのリスト (私は C++ で std::vector を使用します) を持っていることです。リストは、さまざまなサブクラスを認識できるオブジェクトによって管理されます (多くの場合、管理オブジェクト クラス内のプライベート クラスの小さな階層です)。それらを 1 つのリスト (配列、ベクターなど) に保持することで、ポリモーフィズムの恩恵を受けることができますが、操作が特定のサブクラスのオブジェクトに対して作用することを意図している場合は、動的キャストなどを使用します。

私が見逃している動的キャストや型チェックなしで、この種の問題に対する別のアプローチはありますか? これらを何としても回避するプログラマーが、それらをどのように処理するのか、私は本当に興味があります。

私の説明が抽象的すぎる場合は、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」があります(EntityContactの代わりに基本クラスEventHandlerのサブクラスを使用)。

4

6 に答える 6

8

少なくとも私の観点からはdynamic_cast、理由があって存在し、それを使用するのが合理的な場合があります。これは、それらの時間の 1 つかもしれません。

あなたが説明した状況を考えると、考えられる代替手段の1つは、基本クラスで必要な操作をさらに定義することですが基本クラスまたはそれらをサポートしない他のクラスに対してそれらを呼び出すと、(おそらくサイレントに)失敗すると定義することです。オペレーション。

本当の問題は、このように操作を定義することに意味があるかどうかです。典型的な動物ベースの階層に戻ると、Bird を使用している場合、Bird クラスでflyメンバーを定義するのが賢明であることが多く、飛べない少数の鳥については失敗させるだけです (理論的には名前を変更する必要があります)。のようなものですattempt_to_flyが、それで多くのことが達成されることはめったにありません)。

これが多く見られる場合は、クラスに抽象化が欠けていることを示している傾向があります。たとえば、flyまたはの代わりに、メンバーattempt_to_flyが本当に必要な場合がありtravel、それを行うかどうかを決定するのは個々の動物次第です。泳ぐ、這う、歩く、飛ぶなどによって

于 2013-03-21T15:22:25.663 に答える
0

これ

しかし、操作が特定のサブクラスのオブジェクトに作用することを意図している場合、私は動的キャストまたは同様のものを使用します

オブジェクトモデリングが正しくないようです。リストにサブクラスインスタンスが含まれていますが、それらすべてを同じ方法(Liskovなど)で操作することはできないため、実際にはサブクラスではありません。

考えられる解決策の1つは、特定のサブクラスがオーバーライドできる一連のno-opメソッドを持つように基本クラスを拡張することです。しかし、それでも完全には正しく聞こえません。

于 2013-03-21T15:16:38.993 に答える
0

悪いプログラミングと見なされる他のアプローチと同様に、常にいくつかの例外があります。プログラマーが「そのようなものは悪であり、決してそれをすべきではない」と言うとき、それは実際には「そのようなものを使用する理由はほとんどないので、あなたは自分自身をよりよく説明できる」という意味です。私自身、adynamic_castが絶対に必要であり、簡単にリファクタリングできない状況に遭遇したことはありません。しかし、それが仕事を成し遂げるなら、そうです。

私たちはあなたの特定の問題を知らないので、私はあなたが私たちに言ったことを与えられたアドバイスを与えることができるだけです。多形ベースタイプのコンテナがあると言います。例えば:

std::vector<Base*> bases;

このコンテナに派生した特定の方法で配置したオブジェクトのいずれかを使用する場合、それは実際にはベースオブジェクトのコンテナではありませんか?オブジェクトへのポインタのコンテナを持つことの全体的なポイントは、Baseそれらを繰り返し処理して、それらすべてをBaseオブジェクトとして扱うことができるということです。dynamic_cast何らかのタイプにする必要がある場合は、sのDerivedポリモーフィックな動作を悪用していますBase*

Derivedから継承する場合Base、 -aです。簡単に言えば、型へのポリモーフィックポインターを使用する場合は、その型の機能のみを使用する必要があります。Derived BaseBaseDerivedDerivedBase

于 2013-03-21T15:25:19.580 に答える
0

質問は非常に広いので、考慮すべき一般的な点がいくつかあります。

  • これは、有効なユースケースがあることを意味する言語の機能です。
  • 一般的に、それは本質的に良いとか悪いということではありません。
  • それが問題の正しい解決策かどうかを確認してください
  • 問題を解決するための代替手段を知っておいてください。
  • それを他の言語と比較すると、C++/C++11 の代替が他の言語でも可能かどうかという問題が含まれるはずです。
  • dynamic_cast一定の費用がかかります。実行時のパフォーマンスに関する場合は、代替のコストと比較して確認してください。
  • チェックは実行時に実行されるため、適切にテストされていない場合、バグのあるソフトウェアを配布するリスクが生じます。静的型チェックを使用すると、コンパイラは、特定の問題/バグが発生しないことを保証するのに役立ちます。
于 2013-03-21T15:19:06.223 に答える