C++の継承の詳細解説 - 基底クラス派生クラスのオブジェクト代入変換、ダイヤモンド仮想継承

こんにちは、バンバンです。今日は相続について話しましょう。

オブジェクト指向の 3 つの主要な機能: カプセル化、継承、ポリモーフィズム。 


目次

1. 継承の概念と定義

  1.1 継承の概念

  1.2 継承の定義

        1.2.1 フォーマットの定義

        1.2.2 継承とアクセス修飾子

        1.2.3 継承された基本クラスのメンバーのアクセス方法の変更

2. 基底クラスと派生クラスのオブジェクト代入変換

3. 継承の範囲

4. 派生クラスのデフォルトのメンバー関数

5. 継承とフレンドと静的メンバー

  5.1 相続と友人

  5.2 継承と静的メンバー

6. 継承モデル

6.1 単一継承

  6.2 多重継承

  6.3 ダイヤモンドの継承

        6.3.1 ダイヤモンドの仮想継承

        6.3.2 ひし形仮想継承の原理

7. 継承の概要


1. 継承の概念と定義

  1.1 継承の概念

        継承(継承) メカニズムは、オブジェクト指向プログラミングでコードを再利用可能にするための最も重要な方法であり、プログラマ が元のクラスの特性を維持したまま機能を拡張および追加し、派生クラスと呼ばれる新しいクラスを生成できるようにします。継承によるオブジェクト指向の実現
プログラミングの階層構造は、単純なものから複雑なものまでの認知プロセスを反映しています。私たちが以前に経験した再利用は、関数の再利用です
継承はクラス設計レベルの再利用です
        
         継承と は簡単に言うと、 複数 のクラスの同じ情報を同じ共通クラス(親クラス/基底クラス)に入れて、サブクラス/派生クラスで再利用することです。

  1.2 継承の定義

        1.2.1 フォーマットの定義

person は親クラスであり、基本クラスとも呼ばれます。Student はサブクラスであり、派生クラスとも呼ばれます。

        1.2.2 継承とアクセス修飾子

        1.2.3 継承された基本クラスのメンバーのアクセス方法の変更

クラスメンバー/継承
パブリック継承
保護された継承 私的継承
基本クラスのパブリックメンバー
派生クラスのパブリック メンバー
派生クラスの保護されたメンバー
派生クラスのプライベート メンバー
基本クラスの保護されたメンバー
派生クラスの保護されたメンバー
派生クラスの保護されたメンバー
派生クラスのプライベート メンバー
基本クラスのプライベートメンバー
派生クラスでは表示されません
派生クラスでは表示されません
派生クラスでは表示されません

オフトピック: 基本クラスのプライベート メンバーは、継承メソッドに関係なく非表示であることがわかります。実際、これは C++ ボスの落とし穴です。ボスはすべての状況を設計に含めようとしますが、実際の Inほとんどの場合、ほとんどのプロジェクトはパブリックを使用し、プライベートはほとんど使用しませんが、言語の発展が進んでいるために、上司が落とし穴を避けるために言語をロールバックすることはほとんど不可能です (Python3 が Python2 と互換性がないことを除く)。この穴を埋める方法を考えてください。それが穴であることはわかっていますが、私たちはまだ学ばなければなりません。

要約:

  1. 基本クラスのプライベート メンバーは、継承方法に関係なく、派生クラスには表示されません。ここでの 非可視性とは、基本クラスのプライベート メンバーは引き続き派生クラス オブジェクトに継承されますが、派生クラス オブジェクトはクラスの内外に関係なくアクセスできないように文法的に制限されていることを意味します 
  2. 基本クラスのプライベート メンバーには、派生クラスではアクセスできません。基本クラスのメンバーにクラス外から直接アクセスしたくないが、派生クラスではアクセスできるようにする必要がある場合、そのメンバーは保護済みとして定義されますprotected メンバー修飾子は継承によるものであることがわかります
  3. 実際、上記の表を要約すると、基本クラスのプライベート メンバーがサブクラスでは表示されないことがわかります。サブクラス内の基本クラスの他のメンバーのアクセス メソッド == Min (基本クラスのメンバーのアクセス修飾子、継承メソッド) 、public > protected > private。
  4. キーワードclass を使用する場合、デフォルトの継承メソッドは private でありstruct を使用する場合、デフォルトの継承メソッドは public です継承メソッドを明示的に記述することをお勧めします
  5. 実際には、パブリック継承が一般に使用され、プロテクト/プライベート継承はめったに使用されません。また、プロテクト/プライベート継承メンバーは派生クラスでのみ使用できるため、継承の使用は推奨されません。

トピック:

継承できないクラスを定義するにはどうすればよいですか?

答え:

C++98: 親コンストラクターのプライベート - サブクラスは表示されません。サブクラス オブジェクトはインスタンス化されており、コンストラクターを呼び出すことはできません。

C++11: 新しく追加されたキーワード Final (最終クラス)


2. 基底クラスと派生クラスのオブジェクト代入変換

  • 派生クラスのオブジェクトは、基本クラスのオブジェクト/基本クラスのポインター/基本クラスの参照に割り当てることができますここにはスライスまたはカットと呼ばれる比喩的な用語があります派生クラスの親クラス部分を切り取って過去に代入するということです。
  • 基本クラスのオブジェクトを派生クラスのオブジェクトに割り当てることはできません。
  • 基本クラスのポインタまたは参照は、キャストを通じて派生クラスのポインタまたは参照に代入できます。ただし、基本クラスのポインタが派生クラス オブジェクトを指している場合は安全でなければなりません。ここで、基本クラスがポリモーフィック型の場合、RTTI (Run-Time Type Information) の Dynamic_cast を使用して識別し、安全な変換を実行できます。

1. サブクラス オブジェクトは、親クラスのオブジェクト/ポインターおよび参照に直接割り当てることができます。

Student sobj ;
// 1.子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj ;
Person* pp = &sobj;
Person& rp = sobj;

2. 基本クラスのオブジェクトを派生クラスのオブジェクトに割り当てることはできません。

//2.基类对象不能赋值给派生类对象 
sobj = pobj;//错误

3. 強制型変換により、基底クラスのポインタを派生クラスのポインタに代入できる

 pp = &sobj
 Student* ps1 = (Student*)pp; // 这种情况转换时可以的。
 ps1->_No = 10;
    
 pp = &pobj;
 Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
 ps2->_No = 10;

3. 継承の範囲

1. 継承システムでは、基底クラス派生クラスは独立したスコープを持ちます
2. サブクラスと親クラスに同じ名前のメンバーがあり、サブクラスのメンバーは、親クラスから同じ名前のメンバーへの直接アクセスをブロックします 。この状況を 隠蔽と呼びます
再定義とも言います。(サブクラスのメンバー関数では、BaseClass::BaseClassMember Explicit Access を使用できます)
3. メンバー関数 hidden の場合、 hiding を構成するには同じ関数名のみが必要であることに注意してください
4. 実際には、継承システムで同じ名前のメンバーを定義しないことが最善であることに注意してください。

4. 派生クラスのデフォルトのメンバー関数

  • コンストラクタ

サブクラス自体のメンバーはクラスと同じです。(組み込み型は処理しません -> ランダムな値、デフォルト値を与える、デフォルト値が処理します。カスタム型はその処理を呼び出します)

親クラスのメンバーを継承するには、親クラスのコンストラクターを呼び出す必要があります。基本クラスにデフォルトのコンストラクターがない場合、サブクラス コンストラクターの初期化リスト フェーズ中に呼び出しを明示的に行う必要があります。

サブクラスを構築する前に、まず親クラスを構築します。

サブクラスは、初期化リストで親クラスのメンバー変数を直接初期化することはできません。また、匿名オブジェクトは初期化リストで初期化されます。

class Person
{
public:
    Person(const char* name)
	    : _name(name)
	{
		cout << "Person()" << endl;
	}
protected:
    string _name;
}
class Student:public Person
{
public:
    Student(const char* name, int num)
	    :Person(name)    //匿名对象在初始化列表初始
	    , _num(num)
    {
	    cout << "Student()" << endl;
    }
protected:
    int _num;
}
  • デストラクター

同上

これは、親クラスのデストラクターを明示的に呼び出すことなく、自動的に呼び出されます。最初に子クラスが破棄され、次に親クラスが破棄されます。

後続のシーン デストラクターの一部は書き換える必要があるため、書き換えの条件の 1 つは関数名が同じであることです 次に、コンパイラはデストラクタ名に対して特別な処理を実行し、 destructor() として処理します 。そのため、親クラスのデストラクタが virtual を追加しない場合、子クラスのデストラクタと親クラスのデストラクタは隠された関係を形成します。

  • コピーコンストラクター

クラスやオブジェクトなどの独自のメンバー。(組み込み型はコピー -> 浅いコピーを参照し、カスタム型はそのコピー構築を呼び出します)

継承された親クラスのメンバーは、親クラスのコピー コンストラクターを呼び出して初期化する必要があります。

  • 演算子=

 同上

 親クラスのコピーを完了するには、operator= は親クラスの Operator= を呼び出す必要があります。

5. 継承とフレンドと静的メンバー

  5.1 相続と友人

フレンド関係は継承できません。つまり、親クラスのフレンドはサブクラスのプライベートおよび保護されたメンバーにアクセスできません。

  5.2 継承と静的メンバー

基本クラスが静的静的メンバーを定義している場合、そのようなメンバーは継承システム全体で 1 つだけ存在します子供が何人派生しても
クラスには静的メンバー インスタンスが 1 つだけあります。

6. 継承モデル

6.1 単一継承

単一継承: サブクラスに直接の親クラスが 1 つだけある場合、その継承関係は単一継承と呼ばれます。

  6.2 多重継承

多重継承: サブクラスに 2 つ以上の直接の親クラスがある場合、その継承関係は多重継承と呼ばれます。


トピック:

 多重継承: (オブジェクト分散) 最初の継承が前にあり、後の継承が後ろにあります。

スライス:

 p1 と p3 が実際には同じ場所を指していることがわかります。

メンバ変数はスタック上に置かれるローカル変数で、input要素がスタックの先頭に置かれ、アドレスが徐々に増えていきますが、b2は後から引き継がれるので、b1よりアドレスが大きい、つまりp2の方が上位になりますp1よりも。


  6.3 ダイヤモンドの継承

ダイヤモンド継承は多重継承の特殊なケースです
ダイヤモンド継承の問題: 次のオブジェクト メンバー モデルの構築から、ダイヤモンド継承にはデータの冗長性 (_name が繰り返し含まれる) と曖昧さ (どの _name がアクセスされるかを知るのは不可能) があることがわかります。

        6.3.1 ダイヤモンドの仮想継承

仮想継承 (仮想) は、ダイヤモンド継承のあいまいさとデータの冗長性の問題を解決できます。
上記の継承関係と同様に、Student と Teacher が person を継承するときに仮想継承を使用することで問題を解決できます。
ダイヤモンド仮想継承オブジェクト モデル:

        6.3.2 ひし形仮想継承の原理

ここでは、研究のために簡略化されたダイヤモンド継承モデルを示します。

class A
{
public:
	int _a;
};

//class B : public A
class B : virtual public A
{
public:
	int _b;
};

//class C : public A
class C : virtual 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._a = 0;
	d._b = 3;
	d._c = 4;
	d._d = 5;

	return 0;
}
  • まず、仮想継承 virtual を使用しない場合、メモリ状況は次のようになります。

 格納状況は、上で示したダイヤモンド型の継承オブジェクト モデルに準拠します。

  •  仮想継承 virtual を使用すると、メモリの状況は次のようになります。

D オブジェクトでは、A がオブジェクト構成の最下位に配置されていることがわかります。この A は、B と C に同時に属します (共通 A)。B と C が A を格納するアドレスは 2 つあります。この 2 つのアドレスは、 (つまり、仮想実表ポインタ) はテーブル (仮想実表) を指し、オフセットは仮想実表に格納され、オフセットを通じて共通の A が見つかります。

7. 継承の概要

        1. C++ の構文は複雑であり、多重継承がその現れです。多重継承にはひし形の継承があり、ひし形の継承にはひし形の仮想継承があり、基礎となる実装は非常に複雑です。したがって、通常、多重継承を設計することは推奨されません。また、ダイヤモンド継承を設計してはなりません。そうしないと、複雑さとパフォーマンスに問題が生じます。
        2. 多重継承は C++ の欠陥の 1 つであると考えられており、その後の多くの言語には多重継承がありません。

        3.継承と合成 

  • パブリック継承はis-a関係です。つまり、すべての派生クラス オブジェクトは基本クラス オブジェクトです。例: 人々 - 学生。
  • 構成はhas-a の関係です。B が A を構成すると仮定すると、各 B オブジェクトの中に A オブジェクトが存在します。例: タイヤ - 車。
  • クラスの継承よりもオブジェクトの合成を優先します。
  • 継承を使用すると、基本クラスの実装に関して派生クラスの実装を定義できます。派生クラスの生成によるこの種の再利用は、多くの場合、ホワイトボックス再利用と呼ばれます。「ホワイト ボックス」という用語は可視性と関連しています。継承では、基本クラスの内部詳細がサブクラスから見えます。継承により基底クラスのカプセル化はある程度破壊され、基底クラスの変更は派生クラスに大きな影響を与えます。派生クラスと基底クラスとの依存関係は非常に強く、結合度は高い
  • オブジェクトの合成は、再利用のためのクラス継承の代替手段です。オブジェクトを組み立てたり組み合わせたりすることで、より複雑な新しい機能を得ることができます。オブジェクトを合成するには、合成されるオブジェクトに明確に定義されたインターフェイスが必要です。このスタイルの再利用は、オブジェクトの内部の詳細が目に見えないため、ブラックボックス再利用と呼ばれます。オブジェクトは「ブラック ボックス」としてのみ表示されます。複合クラス間には強い依存関係はなく、結合度は低いオブジェクトの構成を優先すると、各クラスをカプセル化した状態に保つことができます。
  • 実際には、できるだけ多くの組み合わせを使用してください。組み合わせの結合度が低く、コードの保守性が良い。ただし、継承も便利です。一部の関係は継承に適しているため、継承を使用します。また、多態性を実現するには、継承も必要ですクラス間の関係は、継承を使用することも、組み合わせを使用することも、組み合わせを使用することもできます。

おすすめ

転載: blog.csdn.net/bang___bang_/article/details/130677253