私はもともとStackOverflowで この質問 を尋ねましたが、私はここに指示されました、そして私の問題はおそらく技術的なものと同じくらい概念的であると思うので、ここに行きます。
C++で抽象クラスの階層を定義し、実装を含む具象サブクラスを作成する場合、次のような抽象クラスになる可能性があります。
A
/ \
B1 B2
具象クラスは次のように継承します:
B1 B2 B1 B2
| | | |
C1 C2 D1 D2
そして、Cn
とDn
がBn
のインターフェースを実装しているとき、またはC1
およびC2
A
インターフェースを実装します別の方法。
ただし、いくつかのshared機能をC1
およびC2
、これはA
インターフェースから来ていますが、どこに置くのですか?
A
は抽象的であり、A
はnotを継承する必要があるため、Dn
に入れることはできません。
概念的なものがあるようですA_for_C
実装ですが、これは別の祖先クラスに属していますか?または構成された兄弟クラスで?
_____A_____ _____A_____
/ | \ / | \
B1 A_for_C B2 vs B1 B2 A_for_C
|_____/ \____ | | |
C1 C2 C1 C2
(C1 and C2 then each have an A_for_C and delegate)
最初のものは概念的には正確に見えますが、virtual
継承が必要ですが、2つ目は委任が必要です。したがって、どちらも実際のあいまいさはありませんが、パフォーマンスに影響を与えます。
Webの周りを読んで、 このWebサイトで と言われている
一部の人々は、継承の目的はコードの再利用であると信じています。 C++では、これは誤りです。明確に述べれば、「継承はコードの再利用のためではありません。」
それでは、実装はどのように共有されるべきですか?
さらなる考え
私はこれらの質問でいくつかの関連する議論を見つけました:
以下のutnapistimの回答は、これらよりもはるかに内容が明確で、他の質問/回答が議論する多くのことを精神的に切り抜けるのに役立ちました。
継承とは、契約を履行することに同意することです。サブクラスが実際に親コントラクトの履行を保証する場合、多重継承は問題ありません。
ただし、実装は実際には最終オブジェクトの問題にすぎません。はい、実装を継承すると便利な場合がありますが、それは実際にはインターフェースに直角であり、デフォルトのvテーブルベースのアプローチ以外にも、実装を取り込むためのさまざまな手法があります。
(1つはコンパイル時で、もう1つは実行時であることを除いて、どちらも同等だと思います。)
あなたの質問には2つの側面があります:
§1。 C++継承とは(コードの再利用でない場合)
ここで最も簡単な答えは、「契約の実装」です( Liskov置換の原則 および ファサードの設計パターン も参照)。
§2。それでは、実装はどのように共有されるべきですか?
考慮してください:
A_for_C
はA
から継承する必要はありません。「継承」問題のほとんどはその名前にあると思います。特にC++を扱う場合、マルチパラダイム言語としては、必ずしもOOPパラダイム、パターン、および用語に従う必要はありません。
パラダイム固有の用語とは別に、C++は2つの「構成」(単純な英語の意味付け)メソッドを提供します。
struct A { B m; } a; a.m.fn(); // B::fn
)struct A: B {} a; a.fn(); // B::fn
)(OOP用語に「注意をそらさない」ためだけに、「構成」と「継承」を使用しませんでした)
2つ目は1つ目と同じデータ構造レイアウトを生成し、m
名を暗黙的に作成し、Aを暗黙的にBのように動作させます。仮想関数を機能させない場合、これは単なるaです。 A自体にコードを追加せずに、B定義の動作をAにインポートする方法。
ここでは、代替原則を適用する意図はありません。これにはOOPの概念はありません。これは、OOP学校が「継承」と呼んでいるものではありません。偶然にもOOP学校とC++言語仕様で指定されたのと同じ名前が付いています。
_std::true_type
_は_std::integal_constant
_を継承しています。それらのいずれにも仮想メソッド(デストラクタを含む)はありません。 OOPの純粋主義者から、これは冒涜ですが、神経質ではなく、C++標準ライブラリの一部です。
仮想関数を動作させると、動作を別の動作で「オーバーライド」する機能を獲得できます(未定義として宣言される場合があります)。これにより、C++クラスはOOP学校が「オブジェクト」と呼ぶものと非常に似たものになり、それらが「継承」と呼ぶものを暗黙的に埋め込みます。
しかし、C++は、(仮想関数と共に)最も頻繁に無視される別の「置換メカニズム」を提供します。は単一継承言語では意味が少ないため無視されることがよくありますが、多重継承で機能します。
このことを考慮:
_struct Inteface_A
{
virtual fnA() = 0;
virtual ~Inteface_A() {};
};
struct Implementation_A_comon
{
virtual fnA() { cout << "common implementation of IA" << endl; }
virtual ~Implementation_A_comon() {}
};
struct Implementation_A_special
{
virtual fnA() { cout << "special implementation of IA" << endl; }
virtual ~Implementation_A_special() {}
};
class Actual_object:
public Inteface_A,
protected Implementation_A_comon,
public Interface_B, //not declared here, but may be in another header
protected Implementation_B_common // not declared here, may be in yet another header
{
};
class Another_object:
public Inteface_A,
protected Implementation_A_special
{
};
_
ここでは、Actual_objectとAnother_objectの両方が_Inteface_A
_の有効な代替になる可能性があります。fn
はpure(他の文献では抽象)ia->fn()
の呼び出しは、派生オブジェクトから継承された唯一の有効なfnメソッドでend-upの呼び出しを行います。そして、それは継承された "implementation"が継承した(C++の純粋な意味で)クラスによって提供されるものです。これはdominace
です。
これがいくつかの異なるインターフェースといくつかの異なる "部分実装"で拡張され、さまざまな方法でお互いを継承し、最終的にオブジェクトにインポートされる異なる動作を提供することで簡単に想像できます。
オーソドックスなOOPですか?絶対にありません。 「有効なOOP」ですか?インターフェースに関してはyes、実装に関してはno(または、少なくとも適切ではない)。これは有効なC++ですか?はい。また、適切なコードの再利用(実装する同じインターフェイスを持つすべてのオブジェクトに何度も書き換える実装はありません)を使用します。実行時タイプのポリモーフィズムを使用し、テンプレートや静的ポリモーフィズムを避け、異なるオブジェクトタイプが必要な場合には適切ではありません。同じランタイムコンテキストに共存:CRTP _IA<A>
_と_IA<B>
_はどちらもIA
という名前で、同じインターフェイスを持っていますが、ランタイムの観点からは無関係です。両方を参照できる_std::vector<something>
_は使用できません。
純粋なOOP学校は、単に自分のプログラマがこれをすべて理解することを信頼していないからといって、単に受け入れません。しかし私にとっては...それはMIのせいではなく、彼らのせいです。