同一の基本クラスを持つ派生クラスの多重継承
多重継承を行うと、基本クラスに指定したクラスの要素をすべて引き継ぐことになります。以下のような継承関係を見てみましょう。クラス derived1もderived2も同一のbaseクラスを継承しています。これらふたつのクラスを多重継承してderived3クラスを定義します。
class base { int a; }; class derived1 : public base { ... }; class derived2 : public base { ... }; class derived3 : public derived1, public derived2 { ... };このように作成されたderived3クラスのメモリレイアウトは以下のようになり、baseクラスのメンバが重複して含まれます。baseクラスのメンバ a にアクセスしようとしたときには、先の重複したメンバ名と同一のあいまいさが発生します。この場合には、derived1::a, derived2::a のようにスコープ解決演算子を使って、あいまいさを解消する必要があります。
仮想基本クラス
基本クラスの要素が派生クラスに複数含まれてしまうのが好ましくない場合も存在します。先のStudentクラスを例にとって説明しましょう。
Studentクラスは、学生の名前、課題1、課題2の得点、平均点を管理するクラスでした。ある人はStudentを継承して課題3の得点を管理できるようにしたStudentAクラスを作成したとしましょう。また別のひとは、Studentを継承して課題4の得点を管理できるStudentBクラスを作成したとします。
ここで、課題1~4の4つの課題を管理したいStudentCクラスを作成するとします。せっかくですのですでに作成済みのStudentA,StudentBクラスを利用したいですよね。そこでStudentA、StudentBクラスを継承してStudentCクラスを作成すると・・・
このような継承関係になり、Studentクラスのメンバを重複して持ったクラスとなってしまいます。これではメモリの無駄も甚だしいですよね。このような無駄を解消し、継承階層の祖先にいる基本クラスのメンバは重複して持たないようにする機能を仮想基本クラスと呼びます。
StudentAクラスとStudentBクラスを作成するときに、以下のようにキーワード virtual を指定します。
class StudentA : virtual public Student { private: int kadai3; public: .... .... }; class StudentB : virtual public Student { private: int kadai4; public: .... .... };このキーワードは、「これからさき誰かが僕を継承して新しいクラスを作るかもしれないけど、その時には基本クラス(Studentクラス)の要素は重複させないでね。」ということをコンパイラに示しています。この時の継承階層は以下のように表します。
練習問題 5
仮想基本クラスの説明にあるような、StudentCクラスを定義し、動作確認できるプログラムを作成せよ。新規に p3k3_5というプロジェクトを作成し、練習問題3-1で使用したStudentクラスをプロジェクトに追加して使用すること。(student.cpp,student.hだけ使う。StudentEXクラスなどは使用しない。)また,以下の点を満たすこと.
- Studentクラスに課題3の得点(int kadai3)を加えたStudentAクラスを作成しなさい。(virtualで派生すること)
- Studentクラスに課題4の得点(int kadai4)を加えたStudentBクラスを作成しなさい。(virtualで派生すること)
- StudentAクラスとStudentBクラスを多重継承し、StudentCクラスを作成しなさい。
- CalcAve関数やGetSum関数が正しく動作するように、StudentCクラス内で関数を再定義すること。
- 各クラスのサイズを調べなさい。また、Studentを仮想基本クラスとせずに継承した場合の各クラスのサイズも調べなさい。(Studentを仮想基本クラスとしない場合,virtualを外すだけでは名前が衝突してコンパイルが通らなくなる場合がある。この課題ではサイズが分かればよいので,影響を受ける箇所は一時的にコメントアウトして良い。)
作成したStudentCクラスの動作確認用のコードの例(main関数)を以下に示す。
#include <iostream> using namespace std; #include "studentC.h" void main() { StudentC obj; obj.SetName("taro"); obj.SetKadai1(90); obj.SetKadai2(30); obj.SetKadai3(80); obj.SetKadai4(12); obj.CalcAve(); cout << obj.Name() << endl; cout << "kadai1:" << obj.GetKadai1() << " kadai2:" << obj.GetKadai2() << " kadai3:" << obj.GetKadai3() << " kadai4:" << obj.GetKadai4()<< endl; cout << "ave:" << obj.Average() << " sum:" << obj.GetSum() << endl; cout << "---------------------------" << endl; //可能であれば、以下のようなコンストラクタを //使った初期化ができるようにトライしてみよう //StudentC obj2("jiro", 20, 40, 50, 64); //obj2.CalcAve(); //cout << obj2.Name() << endl; //cout << "kadai1:" << obj2.GetKadai1() // << " kadai2:" << obj2.GetKadai2() // << " kadai3:" << obj2.GetKadai3() // << " kadai4:" << obj2.GetKadai4()<< endl; //cout << "ave:" << obj2.Average() // << " sum:" << obj2.GetSum() << endl; }