レーキ2:仮想継承

多重継承が物事を複雑にする方法に関する記事。 一見すると、仮想継承は非論理的に実装されます。 一見したように、ロジックが表示されますが、複雑さと複雑さのレベルは増え続けています。 一般に、タスクが難しいほど、ツールを選択するのが簡単になります。



すべては実際のイベントに基づいていますが、問題の本質だけが残るように、例は可能な限り簡略化されています。



そのため、開発されたアプリケーションでは、均一に処理された多数のエンティティが使用されました。 さらに、それらの一部は表示され、他は絶えず更新される必要があり、他はそれらの両方を組み合わせました。 したがって、3つの基本クラスを実装したいという要望がありました。

  1. レンダリング可能 :可視性タグと描画メソッドが含まれます
  2. 更新可能 :アクティビティのサインと状態更新メソッドが含まれます
  3. VisualActivity =レンダリング可能+更新可能


発生した問題を示すために、さらに2つの人工的なクラスを追加します。

  1. JustVisible :単なる可視オブジェクト
  2. JustVisiblePlusVisualActivity :更新可能な状態のJustVisible


次の写真がわかります





問題はすぐに明らかになります-最終クラスはRenderableを2回継承します:JustVisibleとVisualActivityの親として。 これは、表示されたオブジェクトのリストでは正しく機能しません。



JustVisiblePlusUpdate* object = new JustVisiblePlusUpdate; std::vector<Renderable*> vector_visible; vector_visible.push_back(object);
      
      





あいまいな変換が判明します-コンパイラは、どの継承されたレンダリング可能なブランチを把握できません。 彼は、型を中間体の1つに明示的にキャストすることで方向を指定することで助けられる



  JustVisiblePlusUpdate* object = new JustVisiblePlusUpdate; std::vector<Renderable*> vector_visible; vector_visible.push_back(static_cast<VisualActivity*>(object));
      
      





コンパイルは成功し、エラーのみが残ります。 この例では、継承方法に関係なく、同じRenderableが必要でした。 実際、通常の継承の場合、子孫クラス(JustVisiblePlusVisualActivity)には各ブランチの親クラスの個別のインスタンスが含まれます。





さらに、それぞれのプロパティは個別に変更できます。 C ++で表現された、真の表現



 (&static_cast<VisualActivity*>(object)->mVisible) != (&static_cast<JustVisible*>(object)->mVisible)
      
      





そのため、通常の多重継承はタスクに適していませんでした。 しかし、仮想のものは必要な特効薬のように見えました...必要なのは、基本クラスRenderableおよびUpdatableを仮想的に継承し、残りを通常の方法で継承することだけでした



 class VisualActivity : public virtual Updatable, public virtual Renderable ... class JustVisible : public virtual Renderable ... class JustVisiblePlusUpdate : public JustVisible, public VisualActivity
      
      





仮想的に継承されたすべてのクラスは、子孫で一度だけ表されます。 基本クラスにパラメーターを持つコンストラクターがなければ、すべてが機能します。 しかし、そのようなデザイナーが存在し、驚きがありました。 仮想的に継承された各クラスには、デフォルトのコンストラクターとパラメーター化された



 class Updatable { public: Updatable() : mActive(true) { } Updatable(bool active) : mActive(active) { } //.... };
      
      





 class Renderable { public: Renderable() : mVisible(true) { } Renderable(bool visible) : mVisible(visible) { } //.... };
      
      





下位クラスには、パラメーターを持つコンストラクターのみが含まれていました



 class VisualActivity : public virtual Updatable, public virtual Renderable { public: VisualActivity(bool visible, bool active) : Renderable(visible) , Updatable(active) { } //.... };
      
      





 class JustVisible : public virtual Renderable { public: JustVisible(bool visible) : Renderable(visible) { } //.... };
      
      





 class JustVisiblePlusUpdate : public JustVisible, public VisualActivity { public: JustVisiblePlusUpdate(bool visible, bool active) : JustVisible(visible) , VisualActivity(visible, active) { } //.... };
      
      





それでも、オブジェクトを作成するとき



  JustVisiblePlusUpdate* object = new JustVisiblePlusUpdate(false, false);
      
      





デフォルトのレンダリング可能なコンストラクタが呼び出されました! 一見すると、それは野生のように見えました。 しかし、与えられたコードがRenderable :: Renderable()の代わりにコンストラクターRenderable :: Renderable(bool visible)の呼び出しにつながるという仮定がどこから来たかを詳しく見てみましょう。





Renderableが JustVisibleVisualActivity、およびJustVisiblePlusUpdateの奇跡的に分割されたという仮定は、問題を引き起こしました 。 しかし、「奇跡」は起こる運命ではなかった。 結局のところ、それは次のようなものを書くことが可能だろう



 class JustVisiblePlusUpdate : public JustVisible, public VisualActivity { public: JustVisiblePlusUpdate(bool active) : JustVisible(true) , VisualActivity(false, active) { } //.... };
      
      





truefalseのパラメーターを使用したRenderable構築が同時に必要な場合に、競合する情報をコンパイラーに伝えます。 誰もそのようなパラドックスの機会を開くことを望んでおらず、メカニズムは異なる方法で機能しています。 この場合のRenderableクラスは、 JustVisibleまたはVisualActivityの一部ではなく、 JustVisiblePlusUpdateに直接属します。





これは、デフォルトコンストラクターが呼び出された理由を説明しています-仮想クラスコンストラクターは最終相続人、つまり 作業オプションは次のようになります

 class JustVisiblePlusUpdate : public JustVisible, public VisualActivity { public: JustVisiblePlusUpdate(bool visible, bool active) : JustVisible(visible) , VisualActivity(visible, active) , Renderable(visible) , Updatable(active) { } //.... };
      
      





仮想継承では、直接の親コンストラクタに加えて、仮想的に継承されたすべてのクラスのコンストラクタを明示的に呼び出す必要があります。 これはあまり明白ではなく、簡単なプロジェクトでは簡単に見落とされる可能性があります。 そのため、もう一度、真実が確認されました。 各クラスに対して1つだけのオープン継承です 。 それは価値がありません。 私たちのケースでは、 RenderableUpdatableの分離を放棄し、1つの基本的なVisualActivityに限定することにしました 。 これにより、ある程度の冗長性が追加されましたが、アーキテクチャ全体が劇的に簡素化されました。すべての仮想および通常の継承のケースを追跡および維持するには、費用がかかりすぎました。



All Articles