1. ホーム
  2. c++

[解決済み] C++で多重継承を避けるべき理由とは?

2022-04-21 18:02:21

質問

多重継承は良い概念ですか、それとも他の方法で代用できますか?

どのように解決するのですか?

多重継承(MIと略す) 匂う ということを意味します。 通常 そしてそれは、メンテナの顔に泥を塗ることになるのです。

概要

  1. 継承ではなく、機能の合成を考える
  2. 恐怖のダイヤモンドに注意
  3. オブジェクトではなく、複数のインターフェースの継承を検討する
  4. 時には、多重継承が正しいこともあります。もしそうなら、それを使いましょう。
  5. コードレビューで多重継承アーキテクチャを擁護できるように準備しておくこと

1. もしかしてコンポジション?

これは継承にも言えることなので、多重継承の場合はなおさらです。

あなたのオブジェクトは、本当に他のオブジェクトを継承する必要があるのでしょうか?A Car を継承する必要はありません。 Engine が動作するように、また Wheel . A Car には Engine と4つの Wheel .

もし、これらの問題を解決するために、コンポジションではなく、多重継承を使うのであれば、何か間違ったことをしたことになります。

2. 恐怖のダイヤモンド

通常、クラス A では BC を継承しています。 A . そして(なぜかは聞かないでください)、誰かが D の両方を継承しなければなりません。 BC .

このような問題に2回遭遇したのは、8年8ヶ月の間ですが、その理由は面白いですね。

  1. どれだけ最初から失敗だったのか(どちらのケースも。 D の両方から継承されるべきではなかった。 BC )、これは悪いアーキテクチャだったからです(実際。 C は存在しないはずなのですが......)。
  2. メンテナがいくら払っていたかというと、C++では、親クラスが A は、孫クラスである D を更新しているため、1つの親フィールド A::field は、2回更新することを意味します。 B::fieldC::field での新しいポインタ)、あるいは何かが静かに間違ってクラッシュして、後に B::field を削除し C::field ...)

C++でvirtualというキーワードを使って継承を修飾することで、上記のような二重レイアウトを回避することができます。

オブジェクト階層では、グラフではなく、ツリー(ノードが1つの親を持つ)としての階層を保つようにする必要があります。

ダイヤモンドの詳細(2017-05-03 編集)

C++の「恐怖のダイヤモンド」の本当の問題点( 設計が健全であると仮定して - あなたのコードをレビューしてください! )、それは 選択する必要があります :

  • クラスにとって望ましいか A がレイアウトに2回存在することは、どのような意味があるのでしょうか?もしイエスなら、是非とも2回継承してください。
  • 一度だけ存在する必要がある場合、仮想的に継承する。

この選択は問題に固有のもので、C++では、他の言語と異なり、言語レベルで設計を強制するドグマなしに、実際にそれを行うことができるのです。

しかし、すべての力がそうであるように、その力には責任が伴います。

3. インターフェイス

ゼロ個または1個の具象クラスとゼロ個以上のインターフェースの多重継承は、通常、上記の「恐怖のダイヤモンド」に遭遇しないので、Okayです。実際、Javaではこのように行われています。

通常、C言語が以下のものを継承している場合、それは AB は、ユーザーが C であるかのように A であるかのように、そして/または B .

C++では、インターフェースは、以下を持つ抽象クラスである。

  1. すべてのメソッドが純粋仮想と宣言されている(= 0で終わる)。 (を削除しました(2017-05-03)。
  2. メンバ変数がない

0個から1個の実オブジェクトと0個以上のインターフェースの多重継承は、quot;smelly"とはみなされません(少なくとも、それほどではありません)。

C++抽象インターフェースの詳細 (2017-05-03編集)

まず、NVIパターンを使ってインターフェースを生成することができるのは 本当の基準は、状態を持たないことです。 (を除いて、メンバ変数がない)。 this ). 抽象インターフェースの目的は、契約(「あなたは私をこう呼ぶことができる、そしてこう呼ぶこともできる」)を公表することであり、それ以上でもそれ以下でもありません。抽象的な仮想メソッドしか持たないという制限は、設計上の選択であって、義務ではないはずです。

第二に、C++では、抽象的なインターフェイスを仮想的に継承することは、(追加のコストと方向性を考慮しても)理にかなっているのです。もしそうしなければ、インターフェース継承が階層内で何度も登場することになり、曖昧さが生じます。

第三に、オブジェクト指向は素晴らしいが、それは そこにある唯一の真理 TM C++で。正しいツールを使い、C++の他のパラダイムが異なる種類の解決策を提供していることを常に念頭に置いてください。

4. 多重継承は本当に必要なのか?

時々、はい。

通常、あなたの C を継承しています。 AB および AB は、無関係な2つのオブジェクト(同じ階層にない、共通点がない、概念が異なるなど)です。

例えば Nodes X,Y,Zの座標を持ち、多くの幾何学的計算が可能で(おそらく点、幾何学的オブジェクトの一部)、各ノードは他のエージェントと通信できる自動化されたエージェントである。

おそらく、あなたはすでに2つのライブラリにアクセスしていて、それぞれが独自の名前空間を持ち(名前空間を使うもうひとつの理由...でも、あなたは名前空間を使っていますよね?)、1つは geo であり、もう一方は ai

つまり、あなたは自分の own::Node の両方から導出されます。 ai::Agentgeo::Point .

このとき、代わりにコンポジションを使うべきではないかと自問すべきだろう。もし own::Node は本当に本当に両方 ai::Agentgeo::Point となると、コンポジションではダメなのです。

そうすると、多重継承が必要になります。 own::Node は、3D空間での位置に応じて他のエージェントと通信します。

(注意点として ai::Agentgeo::Point は、完全に、完全に、完全にUNRELATEDです.... これにより、多重継承の危険性が劇的に減少します)

その他の事例(2017-05-03編集)

その他のケースもあります。

  • 実装の詳細として(できればプライベートな)継承を使用する。
  • ポリシーのようなC++のイディオムでは、多重継承を使用することができます(各パーツが、他のパーツとの通信に this )
  • std::exception からの仮想継承 ( 例外処理に仮想継承は必要なのか? )
  • などです。

コンポジションを使うこともあれば、MIを使った方がいい場合もあります。要は あなたには選択肢がある。責任を持ってやってください(そして、あなたのコードをレビューしてもらってください)。

5. では、多重継承はした方がいいのか?

私の経験では、ほとんどの場合、ノーです。MIは、たとえうまくいっているように見えても、正しいツールではありません。なぜなら、怠惰な人が結果を理解せずに機能を積み重ねてしまうからです(たとえば CarEngineWheel ).

でも、時には、そうですね。そしてその時は、MIよりうまくいくものはないでしょう。

しかし、MIは胡散臭いので、コードレビューで自分のアーキテクチャを守る覚悟が必要です(守るのは良いことです。)