C++ 多重継承 書いて覚えるための初心者自己中記事
多重継承とは
二つ以上のクラスを継承すること
[ class A ] [ class B ]
↑ ↑
[ class C ]
こんな感じ。
実際にやる。
class ABase {//基底クラス① public: virtual ~ABase() {} virtual void AHoge() = 0; }; class BBase {//基底クラス② public: virtual ~BBase() {} virtual void BHoge() = 0; }; class C :public ABase, public BBase {//多重継承クラス public: virtual void AHoge() { cout << "C::AHoge" << endl; } virtual void BHoge() { cout << "C::BHoge" << endl; } }; int main() { C c; ABase& a = c;//基底クラスの型で派生クラスを参照するオブジェクト a a.AHoge();/*基底クラスの型で派生クラスを参照しているオブジェクトが 自身のクラス(基底クラス)のメンバ関数AHoge を呼び出したが メンバ関数AHoge virtual(純粋仮想関数(仮想関数でも良い))なので 参照しているc のクラスの同名関数(オーバーライド)を呼び出した。*/ BBase& b = c; b.BHoge(); system("pause"); }
//output
C::AHoge
C::BHoge
次、なんか良くわからないけど
[ class A ] [ class A ]
↑ ↑
[ class A1 ] [ class A2 ]
↑ ↑
[ class C ]
この場合はどうかってテキストに書いてる。
class A って同じものなのになんで分けて書くんだろう?
class ABase {//基底クラス① public: virtual ~ABase() {} virtual void AHoge() = 0; }; class A1 :public ABase {//基底クラス①-1 public: virtual void AHoge() { cout << "A1 :: AHoge" << endl; } }; class A2 :public ABase {//基底クラス①-2 public: virtual void AHoge() { cout << "A2 :: AHoge" << endl; } }; class C :public A1, public A2 {//派生クラス(多重継承) }; int main() { C c; //スコープ解決演算子方式 c.A1::AHoge();//見方は c.AHoge(); のAHoge に A1:: が付いたという事 c.A2::AHoge(); //アップキャスト方式 A1& a1 = c;//A1クラスの型でcを参照した【これがアップキャスト】 A2& a2 = c; a1.AHoge();/*A1クラスの型でCクラスオブジェクトを参照している A1クラスのメンバ関数AHoge はVirtual(仮想関数)なので Cクラスの同名関数(オーバーライドされた別処理の関数) に行きたいところだが、無い。 なのでA1クラスのから継承されたメンバ関数が使われる。*/ a2.AHoge(); system("pause"); }
//output
A1 :: AHoge
A2 :: AHoge
A1 :: AHoge
A2 :: AHoge
上の状態で class C にもメンバ関数AHogeをオーバーライドする
//基底クラスは同じなので省略 class C :public A1, public A2 {//派生クラス(多重継承) public: virtual void AHoge() { cout << "C :: AHoge" << endl; } }; int main() { C c; //スコープ解決演算子方式 c.A1::AHoge();//class C にもAHogeはあるけど A1::AHoge で指定しているからA1のが呼ばれる c.A2::AHoge(); //アップキャスト方式 A1& a1 = c;//A1クラスの型でcを参照した【これがアップキャスト】 A2& a2 = c; a1.AHoge();/*A1クラスの型でCクラスオブジェクトを参照している A1クラスのメンバ関数AHoge はVirtual(仮想関数)なので Cクラスの同名関数(オーバーライドされた別処理の関数) に行った。*/ a2.AHoge(); system("pause"); }
//output
A1 :: AHoge
A2 :: AHoge
C :: AHoge
C :: AHoge
あ~、これでアップキャストをABase にするとどうなるかって事か。
class C のオーバーライド関数は一旦破棄で。
C c; ABase& a1 = static_cast<A1&>(c);/*A1への参照キャストもアップキャスト それをさらにABaseへアップキャスト*/ ABase& a2 = static_cast<A2&>(c); a1.AHoge(); a2.AHoge();
//output
A1 :: AHoge
A2 :: AHoge
直接ABaseクラスへアップキャストしようとしても
A1 / A2 どっちのクラス経由なのかがわからないからエラーになるのか。
なるほど、さっき書いた
同じクラスなのにどうして二つ分けて書いてるんだろう
がここから始まるのか。
[ class A ] [ class A ]
↑ ↑
[ class A1 ] [ class A2 ]
↑ ↑
[ class C ]
この状態でclass A(基底クラス) にメンバ変数を一つ作って
class A1 / class A2 それぞれの経路から同じメンバ変数に値を代入したらどうなるのかがしたかったのか。
class ABase {//基底クラス① public: ABase() :m_n(0) {} virtual ~ABase() {} void Set(int n) { m_n = n; } protected: int m_n;//ここにA1 / A2 それぞれの経路から値を入れる }; class A1 :public ABase {//基底クラス①-1 }; class A2 :public ABase {//基底クラス①-2 }; //基底クラスは同じなので省略 class C :public A1, public A2 {//派生クラス(多重継承) public: void Show() { cout << A1::m_n << endl; cout << A2::m_n << endl; } }; int main() { C c; A1& a1 = c; A2& a2 = c; a1.Set(1);//それぞれの経路からといったけど片方だけに入れる c.Show(); system("pause"); }
//output
1
0
A1 から入れたらA1のほうだけ値が入った。
クラスの継承って、基底クラスのメンバをごっそりコピーして派生クラスでも使えるようなイメージだったけど。
違うのか。
基底クラスのメンバは継承しても基底クラスにある感じかな。
class A1 も class A2 もそれぞれに ABase という基底クラスを持っていて
それぞれが別のものだったのか。
さっきのツリーで考えると、確かに class A は二つで間違いない。
[ class A ] [ class A ]
↑ ↑
[ class A1 ] [ class A2 ]
↑ ↑
[ class C ]
この場合 class C というのは
[ class A ] のオブジェクト
↑
[ class A1 ]のオブジェクト
↑
を内包している
&
[ class A ] のオブジェクト
↑
[ class A2 ]のオブジェクト
↑
を内包している
つまりclass C オブジェクトっていうのは
[ class A ] [ class A ]
[ class A1 ] [ class A2 ]
[ class C ]
これらが混ざりあってるわけじゃなくて
個々のオブジェクトに独立していながらセットになっているオブジェクトなのか。きっとそうだ。
基底クラスのオブジェクトを内包しているクラスが派生クラス。と覚えよう。
んで、このように class ABase が二つになってしまうのが嫌だと、一つで良いという場合の話。
[ class A ]
[ class A1 ] [ class A2 ]
[ class C ]
こんな状態にしたい場合ね。
こんな状態のことを仮想継承というらしい。
virtual 使うのね。仮想だし。
class A1 も classA2 も Class ABase を継承するときに普通に
class A1 : public ABase{}
class A2 : public ABase{}
としてたけど、これを
class A1 : virtual public ABase{}
class A2 : virtual public ABase{}
にすれば良いと。
OK
なった。
ツリーがひし形になるので
菱形継承(ダイアモンド継承)
というらしい。
仮想継承がある場合の
引数付きコンストラクタ
class ABase {//基底クラス① public: ABase(int x) :m_n(x) {} virtual ~ABase() {} void Show() { cout << m_n << endl; } protected: int m_n; }; class A1 :virtual public ABase {//基底クラス①-1 public: A1() :ABase(1) {} }; class A2 :virtual public ABase {//基底クラス①-2 public: A2() :ABase(2) {} }; //基底クラスは同じなので省略 class C :public A1, public A2 {//派生クラス(多重継承) public: C() :ABase(3) {}/*仮想継承がある場合class C のオブジェクトを作っても クラスA1とクラスA2とでABaseに対する初期化がバッティングするので そこは呼ばれなくなる なのでclass C でもコンストラクタにはABase のコンストラクタを呼ぶ処理を行う*/ }; int main() { A1 a1; A2 a2; C c; a1.Show(); a2.Show(); c.Show(); system("pause"); }
仮想継承でなければ、class C の一つ上のclass A1とか class A2とかが
class ABase (基底クラス)の引数付きコンストラクタの対応をしてくれているので class C は のほほん としてられる。
しかし仮想継承の場合class A1 / class A2 は同じ親( ABase )を持っているのと
A1 / A2 クラスそれぞれの主張があって class C は内心穏やかではない。
なので class C は A1 / A2 どちらにも相談できず、仕方ないのでABase に直訴しなければならない。
では、仮想継承で仮想関数の場合
class ABase {//基底クラス① public: virtual ~ABase() {} virtual void AHoge() = 0;//純粋仮想関数 }; class A1 :public ABase {//基底クラス①-1 public: virtual void AHoge() {//仮想関数 cout << "A1" << endl; } }; class A2 :virtual public ABase {//基底クラス①-2 public: virtual void AHoge() {//仮想関数 cout << "A2" << endl; } }; class C :public A1,public A2{//派生クラス(多重継承) }; int main() { C c; c.AHoge();//AHogeへのアクセスがあいまい エラー system("pause"); }
もちろんエラー
こんなふうにしてもダメ
C c; ABase& a = static_cast<A1&>(c); a.AHoge();
もっとわかるようにしなければならない。
class ABase {//基底クラス① public: virtual ~ABase() {} virtual void AHoge() = 0;//純粋仮想関数 }; class A1 :public ABase {//基底クラス①-1 public: virtual void AHoge() {//仮想関数 cout << "A1" << endl; } }; class A2 :virtual public ABase {//基底クラス①-2 public: virtual void AHoge() {//仮想関数 cout << "A2" << endl; } }; class C :public A1,public A2{//派生クラス(多重継承) public: virtual void AHoge() {//class C にも作ってあげる cout << "C" << endl; } }; int main() { C c; c.AHoge(); system("pause"); }
凄く特殊なケースなのかよくあることなのか。初心者にはよくわからない。
多重継承するといろいろ気を使わないといけないみたいだけど、
仮想デストラクタと純粋仮想関数のみのクラスでの多重継承では面倒がない。
この仮想デストラクタと純粋仮想関数のみのクラスを
インタフェースクラス
というらしい。
これだと自由に出来るっぽい。
ここまで。