5

データメンバーのみを含む(関数を含まない)クラスを作成しようとしていますが、それらを多態的にしたいと思います。つまり、基本クラスへのポインターによってオブジェクトを渡すことになり、次の機能が必要になります。それらを特定の派生型に変換します(インスタンスが指定された型でない場合dynamic_cast、結果の値はになります)。NULL

例として、私はアイテムを持っています:

struct Item {
    int x, y;
}

動くアイテムと、テキストを含むアイテムもあります。

struct MovingItem: virtual public Item {
    int speedX, speedY;
}

struct TextItem: virtual public Item {
    std::string text;
}

おそらく、上記の仮想継承を使用する必要があります。これは、移動してテキストを含むアイテムも必要なためですが、トップレベルからの1セットの座標のみが必要ですItem

struct MovingTextItem: virtual public MovingItem, virtual public TextItem {
}

これらはすべて正しく定義できますが、それがどのタイプであるかを確認しようとdynamic_castすると、コンパイラはソースタイプがポリモーフィックではないと文句を言います。Item *

void example(Item *i) {
    MovingTextItem *mti = dynamic_cast<MovingTextItem *>(i);  // error!
}

これは、データメンバーの代わりに仮想関数を使用してすべてを再実装した場合に機能しますが、何もオーバーライドする必要がないため、これは無駄に思えます。

私が考えることができる唯一の回避策はtype、基本クラスにメンバーを追加しItem、を使用する代わりにそれをチェックしdynamic_cast、それが正しいタイプである場合はstatic_cast代わりに使用することです。type(割り当てられた値が競合しないように、どこかですべてのオブジェクトタイプについて知る必要があるという欠点があります。)

これが最善の解決策ですか、それとも別の方法がありますか?

明確化

議論のために、私が各オブジェクトタイプをファイルに書き込んでいると想像してください。 MovingItem1つのファイルにTextItem移動し、別のファイルにMovingTextItem移動し、両方のファイルに移動します。したがって、どのインターフェイスが使用されているかを何らかの方法で判断して正しいファイルに書き込まれない限り、すべてのインターフェイスを実装する基本クラスを使用することはできません。

4

4 に答える 4

6

仮想関数を追加すると、クラスはvtableを取得して機能するようになりますdynamic_cast。ノーオペレーションの仮想デストラクタで十分です。(Torstenが指摘しているように、POD以外のメンバーがいる場合は、これが必要になることもあります。)

しかし、私自身の経験から(そして私はこれに何年も抵抗してきました!)、この特定のケースでは継承に反対することを強くお勧めします。代わりに集計を使用してください。継承よりも構成を優先しますか?いくつかのフォワーダーを作成する必要があるかもしれませんが、これによって得られる柔軟性は報われます(システム全体に影響を与えることなく、後で実装を変更できるという点で)。

どういうわけかスピードとテキストを持ち、魔法によって位置を継承するアイテムでなく、スピードとテキストと位置を持つアイテム考えてみてください。後で速度はあるが位置がわからないアイテムが必要になった場合はどうなりますか?

集約シナリオで結果を達成するには、特定の機能に関するコントラクトを構成する「インターフェース」を定義します。仮想継承も必要ありません。「インターフェース」は、純粋な仮想関数のみを備えた単なるクラスです。参照:「インターフェースにプログラムする」とはどういう意味ですか?(保守可能なソフトウェアが必要な場合のもう1つの重要なルール。)

4つの「インターフェース」(純粋仮想関数のみを持つクラス)を定義できます:Position、、、。前者の3つを継承し、関数自体を定義しません。最初の3つのインターフェースに「リファレンス実装」を提供します。「参照impl」。forには3つのデータメンバー(最初の3つのインターフェイスの参照実装)があり、これらの実装に転送されます。これで、正確に何であるかを知らなくても、たとえば、位置のあるものが必要な場所で使用できます。も動作します。SpeedTextItemItemItemPositiondynamic_cast

ItemBaseまた、仮想メソッドを使用せずにインターフェイスを定義し、このインターフェイスから継承することもPositionできSpeedますText。これにより、同じ基本タイプを介してすべての機能にアクセスし、サブインターフェイスの可用性を動的にテストできます。まだvirtual相続の必要はないと思います...

于 2012-08-18T11:22:53.660 に答える
2

基本クラスに仮想デストラクタを追加することもできますし、おそらく追加する必要もあります。次に、dynamic_castを使用することができます。また、ビジターパターンもご覧ください。このパターンは、非常に小さく、ほとんど変更されないデータ型のセットがあり、そのデータ型に対する操作のセットが豊富な場合に非常に役立ちます。

于 2012-08-18T11:25:48.243 に答える
1

あなたがこれを行うことができるとしましょう。あなたはMovingItem速度を持っています。あなたはTextItem文字列を持っています。そして、おそらく、とからの複数の仮想継承を使用する文字列とベロシティを使用することもできMovingTextItemます。MovingItemTextItem

そして、これらすべてをに貼り付けますstd::vector<Item*>。いいよ。

それをどのように使用しますか?

を使用する必要のあるすべてのコードは、必要な実際のタイプになりますか(したがって、適切なタイプでない場合は終了します)?または、これらの関数の呼び出し元は?いずれにせよ、それはこれらの値を使用するためだけに多くのことです。Item*dynamic_castdynamic_castdynamic_cast

しかし、それはマイナーです。本当の問題はこれです:どのようにそれらを削除しますか?

デストラクタItemがない限り、いずれかを呼び出すことは非常に悪いでしょう。仮想デストラクタがないと、C++には派生クラスのデストラクタを呼び出す方法がありません。したがって、どういうわけか、アイテムの実際のタイプを取得する必要があります。virtual deleteItem*Item*

そして、それには大きな一連のdynamic_cast操作が必要になります。Itemまた、新しい派生クラスを追加するたびdynamic_castに、リストに別のチェックを追加する必要があります。

Itemまたは、仮想デストラクタを指定して、C++にその仕事を任せることもできます。dynamic_castそうすれば、実際のオブジェクトタイプを見つけて削除するためだけに一連のを実行する必要はありません。さらに良いことに、仮想デストラクタがあるため、仮想型になり、C ++を使用すると、やりたいと思われるItemすべてのことを実行できます。dynamic_cast

確かに、dynamic_castこれだけ使用しているという事実は、設計を修正する必要があることを示す即時の危険信号です。

于 2012-08-19T04:02:14.353 に答える
0

念のため、これを再設計して、Item有効なフィールドを示すフラグを使用した単一のタイプを作成しました。

enum ItemType {HasSpeed = 1, HasText = 2};
struct Item {
    int type;
    int x, y;

    int speedX, speedY;
    std::string text;
}

次にtype = HasSpeed | HasText、速度フィールドとテキストフィールドの両方が有効かどうかを設定します。少し古い学校かもしれませんが、もっと簡単です!

于 2012-08-23T01:52:13.873 に答える