[解決済み] C++で多重継承を避けるべき理由とは?
質問
多重継承は良い概念ですか、それとも他の方法で代用できますか?
どのように解決するのですか?
多重継承(MIと略す) 匂う ということを意味します。 通常 そしてそれは、メンテナの顔に泥を塗ることになるのです。
概要
- 継承ではなく、機能の合成を考える
- 恐怖のダイヤモンドに注意
- オブジェクトではなく、複数のインターフェースの継承を検討する
- 時には、多重継承が正しいこともあります。もしそうなら、それを使いましょう。
- コードレビューで多重継承アーキテクチャを擁護できるように準備しておくこと
1. もしかしてコンポジション?
これは継承にも言えることなので、多重継承の場合はなおさらです。
あなたのオブジェクトは、本当に他のオブジェクトを継承する必要があるのでしょうか?A
Car
を継承する必要はありません。
Engine
が動作するように、また
Wheel
. A
Car
には
Engine
と4つの
Wheel
.
もし、これらの問題を解決するために、コンポジションではなく、多重継承を使うのであれば、何か間違ったことをしたことになります。
2. 恐怖のダイヤモンド
通常、クラス
A
では
B
と
C
を継承しています。
A
. そして(なぜかは聞かないでください)、誰かが
D
の両方を継承しなければなりません。
B
と
C
.
このような問題に2回遭遇したのは、8年8ヶ月の間ですが、その理由は面白いですね。
-
どれだけ最初から失敗だったのか(どちらのケースも。
D
の両方から継承されるべきではなかった。B
とC
)、これは悪いアーキテクチャだったからです(実際。C
は存在しないはずなのですが......)。 -
メンテナがいくら払っていたかというと、C++では、親クラスが
A
は、孫クラスであるD
を更新しているため、1つの親フィールドA::field
は、2回更新することを意味します。B::field
とC::field
での新しいポインタ)、あるいは何かが静かに間違ってクラッシュして、後にB::field
を削除しC::field
...)
C++でvirtualというキーワードを使って継承を修飾することで、上記のような二重レイアウトを回避することができます。
オブジェクト階層では、グラフではなく、ツリー(ノードが1つの親を持つ)としての階層を保つようにする必要があります。
ダイヤモンドの詳細(2017-05-03 編集)
C++の「恐怖のダイヤモンド」の本当の問題点( 設計が健全であると仮定して - あなたのコードをレビューしてください! )、それは 選択する必要があります :
-
クラスにとって望ましいか
A
がレイアウトに2回存在することは、どのような意味があるのでしょうか?もしイエスなら、是非とも2回継承してください。 - 一度だけ存在する必要がある場合、仮想的に継承する。
この選択は問題に固有のもので、C++では、他の言語と異なり、言語レベルで設計を強制するドグマなしに、実際にそれを行うことができるのです。
しかし、すべての力がそうであるように、その力には責任が伴います。
3. インターフェイス
ゼロ個または1個の具象クラスとゼロ個以上のインターフェースの多重継承は、通常、上記の「恐怖のダイヤモンド」に遭遇しないので、Okayです。実際、Javaではこのように行われています。
通常、C言語が以下のものを継承している場合、それは
A
と
B
は、ユーザーが
C
であるかのように
A
であるかのように、そして/または
B
.
C++では、インターフェースは、以下を持つ抽象クラスである。
- すべてのメソッドが純粋仮想と宣言されている(= 0で終わる)。 (を削除しました(2017-05-03)。
- メンバ変数がない
0個から1個の実オブジェクトと0個以上のインターフェースの多重継承は、quot;smelly"とはみなされません(少なくとも、それほどではありません)。
C++抽象インターフェースの詳細 (2017-05-03編集)
まず、NVIパターンを使ってインターフェースを生成することができるのは
本当の基準は、状態を持たないことです。
(を除いて、メンバ変数がない)。
this
). 抽象インターフェースの目的は、契約(「あなたは私をこう呼ぶことができる、そしてこう呼ぶこともできる」)を公表することであり、それ以上でもそれ以下でもありません。抽象的な仮想メソッドしか持たないという制限は、設計上の選択であって、義務ではないはずです。
第二に、C++では、抽象的なインターフェイスを仮想的に継承することは、(追加のコストと方向性を考慮しても)理にかなっているのです。もしそうしなければ、インターフェース継承が階層内で何度も登場することになり、曖昧さが生じます。
第三に、オブジェクト指向は素晴らしいが、それは そこにある唯一の真理 TM C++で。正しいツールを使い、C++の他のパラダイムが異なる種類の解決策を提供していることを常に念頭に置いてください。
4. 多重継承は本当に必要なのか?
時々、はい。
通常、あなたの
C
を継承しています。
A
と
B
および
A
と
B
は、無関係な2つのオブジェクト(同じ階層にない、共通点がない、概念が異なるなど)です。
例えば
Nodes
X,Y,Zの座標を持ち、多くの幾何学的計算が可能で(おそらく点、幾何学的オブジェクトの一部)、各ノードは他のエージェントと通信できる自動化されたエージェントである。
おそらく、あなたはすでに2つのライブラリにアクセスしていて、それぞれが独自の名前空間を持ち(名前空間を使うもうひとつの理由...でも、あなたは名前空間を使っていますよね?)、1つは
geo
であり、もう一方は
ai
つまり、あなたは自分の
own::Node
の両方から導出されます。
ai::Agent
と
geo::Point
.
このとき、代わりにコンポジションを使うべきではないかと自問すべきだろう。もし
own::Node
は本当に本当に両方
ai::Agent
と
geo::Point
となると、コンポジションではダメなのです。
そうすると、多重継承が必要になります。
own::Node
は、3D空間での位置に応じて他のエージェントと通信します。
(注意点として
ai::Agent
と
geo::Point
は、完全に、完全に、完全にUNRELATEDです.... これにより、多重継承の危険性が劇的に減少します)
その他の事例(2017-05-03編集)
その他のケースもあります。
- 実装の詳細として(できればプライベートな)継承を使用する。
-
ポリシーのようなC++のイディオムでは、多重継承を使用することができます(各パーツが、他のパーツとの通信に
this
) - std::exception からの仮想継承 ( 例外処理に仮想継承は必要なのか? )
- などです。
コンポジションを使うこともあれば、MIを使った方がいい場合もあります。要は あなたには選択肢がある。責任を持ってやってください(そして、あなたのコードをレビューしてもらってください)。
5. では、多重継承はした方がいいのか?
私の経験では、ほとんどの場合、ノーです。MIは、たとえうまくいっているように見えても、正しいツールではありません。なぜなら、怠惰な人が結果を理解せずに機能を積み重ねてしまうからです(たとえば
Car
と
Engine
と
Wheel
).
でも、時には、そうですね。そしてその時は、MIよりうまくいくものはないでしょう。
しかし、MIは胡散臭いので、コードレビューで自分のアーキテクチャを守る覚悟が必要です(守るのは良いことです。)
関連
-
[解決済み】C++ - 解放されるポインタが割り当てられていないエラー
-
[解決済み] [Solved] Error C1083: Cannot open include file: 'stdafx.h'
-
[解決済み] to_string は std のメンバーではない、と g++ が言っている (mingw)
-
[解決済み】警告 - 符号付き整数式と符号なし整数式の比較
-
[解決済み] using namespace std;」はなぜバッドプラクティスだと言われるのですか?
-
[解決済み] static_cast, dynamic_cast, const_cast, reinterpret_cast はいつ使うべきですか?
-
[解決済み] なぜ、オブジェクトそのものではなく、ポインタを使用しなければならないのですか?
-
[解決済み] インターフェースと抽象クラス(一般的なOO)
-
[解決済み] mixinとは何か、なぜ有用なのか?
-
[解決済み] Pythonのsuper()は多重継承でどう動くのか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】コンストラクターでのエラー:識別子を期待されますか?
-
[解決済み】LLVMで暗黙のうちに削除されたコピーコンストラクタの呼び出し
-
[解決済み】Visual Studio 2015で「非標準の構文。'&'を使用してメンバーへのポインターを作成します」エラー
-
[解決済み】エラー:strcpyがこのスコープで宣言されていない
-
[解決済み】ファイルから整数を読み込んで配列に格納する C++ 【クローズド
-
[解決済み】なぜ、サイズ8の初期化されていない値を使用するのでしょうか?
-
[解決済み] to_string は std のメンバーではない、と g++ が言っている (mingw)
-
[解決済み】Visual Studioのデバッガーエラー。プログラムを開始できません 指定されたファイルが見つかりません
-
[解決済み] 警告:暗黙の定数変換でのオーバーフロー
-
[解決済み】Eclipse IDEでC++エラー「nullptrはこのスコープで宣言されていません」が発生する件