21

私はNonVirtual Interface patternについて読んでいました: Herb Sutter は、ほとんどの場合、仮想関数が非公開でなければならず、場合によっては保護され、公開されてはならない理由について話しています。

しかし、彼は記事の最後に次のように書いています。

具体的なクラスから派生しないでください。または、Scott Meyers が「より効果的な C++」[8] の項目 33 で述べているように、「リーフ以外のクラスを抽象化する」。(確かに、それは実際に発生する可能性があります-もちろん、あなたではなく他の誰かによって書かれたコードで!-そしてこの1つのケースでは、すでに貧弱な設計に対応するためだけに、パブリック仮想デストラクタが必要になる場合があります。リファクタリングする方が良いただし、可能であればデザインを修正してください。)

しかし、なぜこれが悪いデザインなのか理解できません

4

5 に答える 5

20

More Effects C++のコピーを購入するか、ローカル ライブラリでコピーを確認し、項目 33 を読んで完全な説明を読むことができます。そこにある説明は、クラスがスライスとも呼ばれる部分代入を起こしやすくなるというものです。

ここには 2 つの問題があります。まず、最後の行で呼び出された代入演算子は、Animal関連するオブジェクトが type であっても、クラスの代入演算子Lizardです。その結果、 のAnimal部分のみliz1が変更されます。これは部分的な割り当てです。割り当て後、liz1Animalメンバーは から取得した値を持ちますが、liz2liz1メンバーLizardは変更されません。

2番目の問題は、実際のプログラマーがこのようなコードを書くことです。特に C++ に移行した経験豊富な C プログラマーにとって、ポインターを介してオブジェクトに割り当てを行うことは珍しくありません。その場合、割り当てをより合理的な方法で動作させたいと考えています。項目 32 が指摘するように、クラスは正しく使用するのは簡単で、間違って使用するのは難しいはずであり、上の階層のクラスは間違って使用するのは簡単です。

C++ でのオブジェクト スライスの問題の説明については、この質問と他の質問を参照してください。

項目 33 には、後で次のようにも記載されています。

のような具体的な基本クラスAnimalを抽象的な基本クラスのようなものに置き換えると、単に の動作を理解しやすくするAbstractAnimalだけでなく、はるかに多くの利点が得られます。operator=また、配列をポリモーフィックに処理しようとする可能性を減らします。その不快な結果については項目 3 で説明します。ただし、この手法の最も重要な利点は設計レベルで発生します。具体的な基本クラスを抽象基本クラスに置き換えるためです。クラスを使用すると、有用な抽象化の存在を明示的に認識する必要があります。つまり、有用な概念が存在するという事実に気付いていなくても、有用な概念の新しい抽象クラスを作成する必要があります。

于 2013-05-23T22:42:55.693 に答える
6

一般に、2 つの異なる具体的なオブジェクト間の契約を厳密に維持することは困難です。

一般的な動作を扱う場合、継承は非常に簡単で堅牢になります。

Ferrariという名前のクラスと という名前のサブクラスを作成したいとしますSuperFerrari。契約: turnTheKey()goFast()skid()

一見すると、非常によく似たクラスのように聞こえますが、競合はありません。これらの両方の具象クラスの継承に進みましょう。

ただし、次は : に機能を追加しSuperFerrariますturnOnBluRayDisc()

問題: 継承は、コンポーネント間の IS-A 関係を想定しています。この新機能により、独自の動作を持つSuperFerrari単純なものではなくなりました。Ferrariそれは今ADDS動作です。最初に参照を処理しながら新しい機能を選択するために、いくつかのキャストが必要な醜いコードにつながりますFerrari(ポリモーフィズム)。

両方のクラスに共通の抽象/インターフェイス クラスを使用すると、この問題は解消さFerrariれ、 SuperFerrari2 つのリーフになります。これはより論理的であり、類似しているFerrariSuperFerrari見なすべきではありません (もはや IS-A 関係ではありません)。

要するに、具体的なクラスから派生すると、多くの場合、貧弱で醜いコードになり、柔軟性が低下し、読みにくく、維持しにくくなります。

抽象クラス/インターフェースによって駆動される階層を作成すると、具体的な子クラスを問題なく個別に進化させることができます。特に、他のリーフを退屈させることなく特別な量の具体的なクラス専用のサブ階層を実装し、ポリモーフィズムの恩恵を受ける場合は特にそうです。

于 2013-05-23T22:43:01.397 に答える
4

具象型から継承する際の問題は、特定の型を指定するコードが本当に特定の具象型のオブジェクトを必要とするのか、それとも具象型の動作と同じように動作する型のオブジェクトを必要とするのかについて、あいまいさが生じることです。 . この区別は C++ では重要です。特定の型のオブジェクトでは正しく機能する操作が、派生型のオブジェクトではうまく機能しない場合が多いためです。Java と .NET では、特定の型のオブジェクトを使用できても、派生型のオブジェクトを使用できないという状況はほとんどありません。そのため、具象型からの継承はそれほど問題になりません。しかし、そこにも抽象クラスから継承した具象クラスを封印し、

于 2013-05-23T22:59:52.170 に答える
0

具象基本クラスから派生すると、機能が継承されますが、ソース コードがないと明らかにならない場合があります。インターフェイス (または抽象クラス) にプログラムする場合、機能を継承しないため、API の公開などを行うためのより良い方法になります。そうは言っても、具象クラスからの継承が許容される場合はたくさんあると思います

于 2013-05-23T22:40:41.620 に答える