目次
前文
前回に引き続き、相続の基本的な応用について解説しましたが、今回は批判されている相続の欠点であるダイヤモンド型相続についてお話します。
1、単一継承/多重継承
ひし形を接続する前に、単一継承と多重継承について説明する必要があります。
単一継承: サブクラスに直接の親クラスが 1 つだけある場合、その継承関係は単一継承と呼ばれます。
多重継承: サブクラスが 2 つ以上の直接の親クラスを持つ場合、この継承関係は多重継承と呼ばれます。多重継承の出現は主に人生のいくつかのシナリオに対処するためです。たとえば、ミニトマトは果物と野菜の両方の属性を持っています。おそらく家長は、これがダイヤモンド型の相続という大きな問題につながるとは予想していなかったのだろう。
二,菱形継承
次の図に示すように、ダイヤモンド継承は実際には多重継承の変形です。
ひし形軸受にも使用シーンはありますが、利点よりも欠点の方が大きく、使用上の問題点も多く、主に2つの問題点があります。
1.曖昧さの問題 上図に示すように、BとCは両方ともAの要素を持っているので、DがAを呼び出すとき、Aの要素はBの中にあるのか、それとも呼び出すときのAの要素はBの中にあるのか?
2.データの冗長性の問題。上図に示すように、B と C に A があり、BC を継承する D に 2 つの A が存在し、データの冗長性が発生します。
class A
{
public:
int _a;
};
class B :public A
{
protected:
int _b;
};
class C :public A
{
protected:
int _c;
};
class D :public B, public C
{
protected:
int _d;
};
int main()
{
D d;
d._a;
return 0;
}
上図のように、_a にアクセスする場合、曖昧さの問題によりアクセスがあいまいになってしまいますが、先人たちはこの問題をどのように解決したのでしょうか。
3. ひし形の仮想継承
C++3.0 では、ダイヤモンド継承のあいまいさとデータの冗長性の問題を解決するために、 C++ に仮想継承が導入されました。
3.1 仮想継承の使用法
上記の例を例にとると、 B と C が A を継承する場合、先頭にキーワードvirtualを追加するだけで済みます。
class A
{
public:
int _a;
};
class B :virtual public A
{
protected:
int _b;
};
class C :virtual public A
{
protected:
int _c;
};
class D :public B, public C
{
protected:
int _d;
};
int main()
{
D d;
d._a;
return 0;
}
正常に実行されました
3.2 解決原理
では、仮想継承はダイヤモンド継承における曖昧さとデータの冗長性の問題をどのように解決するのでしょうか? メモリ ウィンドウを使用してオブジェクト メンバーのモデルを観察できます。
class A
{
public:
int _a;
};
class B :public A
{
public:
int _b;
};
//virtual
class C :public A
{
public:
int _c;
};
class D :public B, public C
{
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
この実験では、上記のコードを借用して、仮想装飾ありの継承と装飾なしの継承のオブジェクト メンバー メモリ モデルを比較します。
上図は、仮想変更継承を行わないオブジェクト メンバーのメモリ モデルです。
上図は、仮想修飾を加えたメンバー変数のメモリ モデルです。上図から、D オブジェクトが A を下部空間に配置すると結論付けることができます。この A は、B と C の両方に属します。では、B と C はどのようにして A を見つけるのでしょうか。 ? ? ここに、B と C の 2 つのポインターが指すテーブルがあります。ここでのポインタを仮想実表ポインタ、ここでのテーブルを仮想実表と呼び、仮想実表にはBまたはCから公開領域Aまでのオフセットが格納され、BとCはオフセットからAを見つけます。
以下はひし形仮想継承の原理の説明です。
4. Cheng Cheng の要約と考察
1. C++ の構文は複雑であり、多重継承がその良い例です。本来、多重相続の出現は、人生の特定の状況に対処するためのものですが、多重相続の出現により、ダイヤモンド型の相続が発生します。ダイヤモンド型の相続では、ダイヤモンド型の仮想的な相続が必要となるため、基礎となる実装は非常に複雑なので、一般的には 多重継承 を使用することをお勧めします。使用することもできますが、ダイヤモンド型ベアリングを設計する必要はありません。そうしないと、後続の問題の解決が困難になります。
2. 多重継承は C++ 設計の欠陥と考えられるため、Java などの後の多くの OO 言語 (オブジェクト指向言語) には多重継承がありませんでした。
3. 継承と合成
1. パブリック継承はis-a関係です。つまり、すべての派生クラス オブジェクトは基本クラス オブジェクトです。
2. 組み合わせはhas-a の関係にあります。B が A を構成すると仮定すると、各 B オブジェクトの中に A オブジェクトが存在します。
3.クラスの継承よりもオブジェクトの合成を優先します。
4.継承により、基本クラスの実装に基づいて派生クラスの実装を定義できます。派生クラスの生成によるこの種の再利用は、多くの場合、ホワイトボックス再利用と呼ばれます。「ホワイト ボックス」という用語は可視性と関連しています。継承では、基本クラスの内部詳細がサブクラスから見えます。継承により基底クラスのカプセル化はある程度破壊され、基底クラスの変更は派生クラスに大きな影響を与えます。派生クラスと基底クラスとの依存関係は非常に強く、結合度は高い。
5.オブジェクトの合成は、クラス継承以外のもう 1 つの再利用オプションです。オブジェクトを組み立てたり組み合わせたりすることで、より複雑な新しい機能を得ることができます。オブジェクトを合成するには、合成されるオブジェクトに明確に定義されたインターフェイスが必要です。このスタイルの再利用は、オブジェクトの内部の詳細が表示されないため、ブラックボックス再利用と呼ばれます。オブジェクトは「ブラック ボックス」としてのみ表示されます。複合クラス間には強い依存関係はなく、結合度は低い。オブジェクトの構成を優先すると、各クラスをカプセル化した状態に保つことができます。
6.できるだけ多くの組み合わせを使用します。組み合わせの結合度が低く、コードの保守性が良い。ただし、継承も便利です。一部の関係は継承に適しているため、継承を使用します。また、多態性を実現するには、継承も必要です。クラス間の関係には、継承を使用することも、組み合わせを使用することも、組み合わせを使用することもできます。
要約する
以上が相続内容の全てです、鉄人の方に品物が届いて頂ければ幸いです