C++ サブクラスの継承
継承は、クラスが別のクラスのプロパティと動作を取得する OOP の機能です。 別のクラスを継承するクラスはサブクラスと呼ばれ、プロパティが継承されるクラスは基底クラスと呼ばれます。
この記事では、クラスの継承中に発生するひし形の問題について説明します。
C++ サブクラスの継承
継承は、あるクラスが別のクラスのプロパティを継承できるようにする機能です。 別のクラスのプロパティを継承するクラスはサブクラスまたは派生クラスと呼ばれ、プロパティが継承されるクラスは基本クラスまたは親クラスと呼ばれます。
継承の実際の例は、親のプロパティを継承する子です。
C++ の継承には、大きく分けて、単一継承、多重継承、多レベル継承、階層継承、ハイブリッド継承の 5 種類があります。
- 単一継承 - このタイプの継承では、単一のサブクラスが単一の基本クラスから継承されます。
- 多重継承 - このタイプの継承では、サブクラスは複数の基本クラスから継承されます。
- 階層的継承 - このタイプの継承では、複数のサブクラスが同じ基本クラスから継承されます。
- マルチレベル継承 - このタイプの継承では、あるクラスが別のクラスに継承され、そのクラスが別のクラスにも継承されます。 たとえば、
A
、B
、およびC
という 3つのクラスがあり、クラスC
はクラスB
によって継承され、クラスB
はクラスA
によって継承されます。 - ハイブリッド継承 - このタイプの継承では、複数のタイプの継承が組み合わされます。
最初に、C++ における多重継承と階層継承について説明します。
C++ のサブクラスでの多重継承
前述のように、サブクラスは、複数の継承で他の複数の基本クラスによって継承されます。 多重継承を詳細に理解するために、例を挙げてみましょう。
#include <iostream>
using namespace std;
class Base1 {
public:
Base1() { cout << "Base class 1" << endl; }
};
class Base2 {
public:
Base2() { cout << "Base class 2" << endl; }
};
class Derived : public Base2, public Base1 {
public:
Derived() { cout << "Derived class" << endl; }
};
int main() {
Derived d;
return 0;
}
出力:
Base class 2
Base class 1
Derived class
上記のコードには、Base1
と Base2
の 2つの基本クラスがあり、そこから Derived
クラスが継承されます。 ただし、基本クラスのコンストラクターが呼び出される順序に注意する必要があります。
まず、Base2
クラス コンストラクターが呼び出されます。これは、Derived
クラスが最初にそれを継承し、次に Base1
コンストラクターを継承するためです。
C++ のサブクラスでの階層的継承
前述のように、階層継承では単一の基底クラスから複数のサブクラスが継承されます。 階層的な継承を詳細に理解するために、例を挙げてみましょう。
#include <iostream>
using namespace std;
class Base {
public:
Base() { cout << "Base class" << endl; }
};
class Derived1 : public Base {
public:
Derived1() { cout << "Derived class 1" << endl; }
};
class Derived2 : public Base {
public:
Derived2() { cout << "Derived class 2" << endl; }
};
int main() {
Derived1 d1;
Derived2 d2;
return 0;
}
出力:
Base class
Derived class 1
Base class
Derived class 2
上記のコード例には、Derived1
クラスと Derived2
クラスが共通の Base
クラスから継承された 3つのクラスがあります。
C++ の継承におけるダイヤモンド問題
ひし形の問題は、階層継承と多重継承を組み合わせるときに発生します。 この問題は、クラスが互いに継承しながら菱形を形成するため、そう呼ばれます。
A
、B
、C
、および D
の 4つのクラスがあるシナリオがあるとします。
- クラス
A
は基本クラスとして機能します。 - クラス
B
はクラスA
によって継承されます。 - クラス
C
はクラスA
にも継承されます。 - クラス
D
は、クラスB
とクラスC
の両方に継承されます。
次に、コードを使用して発生する問題を見てみましょう。
#include <iostream>
using namespace std;
class A {
public:
A() { cout << "Constructor A here!" << endl; }
};
class B : public A {
public:
B() { cout << "Constructor B here!" << endl; }
};
class C : public A {
public:
C() { cout << "Constructor C here!" << endl; }
};
class D : public B, public C {
public:
D() { cout << "Constructor D here!" << endl; }
};
int main() {
D d;
return 0;
}
出力:
Constructor A here!
Constructor B here!
Constructor A here!
Constructor C here!
Constructor D here!
上記の出力は、クラス D
がクラス A
の 2つのコピーを取得するため、クラス A
のコンストラクターが 2 回呼び出されたことを示しています。 1つはクラス B
を継承し、もう 1つはクラス C
を継承します。
これにより曖昧さが生じ、C++ ではダイヤモンド問題と呼ばれます。
この問題は主に、共通の基本クラスから継承された複数の基本クラスからクラスが継承される場合に発生します。
ただし、この問題は C++ で virtual
キーワードを使用して解決できます。 子クラスが共通の祖父母クラスの 2つのコピーを取得しないように、2つの親クラスが仮想クラスと同じ基本クラスから継承されるようにします。
したがって、コード例では、クラス B
とクラス C
を仮想クラスにしています。
コード例を使用して、この問題の解決策を見てみましょう。
#include <iostream>
using namespace std;
class A {
public:
A() { cout << "Constructor A here!" << endl; }
};
class B : virtual public A {
public:
B() { cout << "Constructor B here!" << endl; }
};
class C : virtual public A {
public:
C() { cout << "Constructor C here!" << endl; }
};
class D : public B, public C {
public:
D() { cout << "Constructor D here!" << endl; }
};
int main() {
D d;
return 0;
}
出力:
Constructor A here!
Constructor B here!
Constructor C here!
Constructor D here!
したがって、上記の出力に示されているように、クラス A
のコンストラクターのコピーを 1つだけ取得しています。 virtual
キーワードは、クラス B
とクラス C
の両方が同じ基本クラス A
から継承していることをコンパイラーに伝えます。 したがって、一度だけ呼び出す必要があります。
まとめ
この記事では、継承と継承の種類について簡単に説明しました。 ただし、主に 2つのタイプの継承について説明しました。つまり、多重継承と階層型継承で、継承のダイヤモンド問題として知られる問題が発生します。
ひし形の問題は、クラスが複数の基本クラスから継承し、それが単一の基本クラスからも継承されている場合に発生します。 ただし、この問題は C++ の virtual
キーワードを使用して解決されます。