C++の基礎 : C++ の基本知識
継承
クラスには継承と呼ばれるメカニズムがあり、これにより既存のクラスの再利用性が高まります。継承を使うと次のようなことが実現できます。
- 既存クラスに機能を追加した新しいクラスを定義する。
- 既存クラスの挙動をカスタマイズした新しいクラスを定義する。
ここでは、継承による機能追加について説明します。継承により既存クラスの挙動をカスタマイズするには、継承とともに仮想関数と呼ばれる機構を使用します。仮想関数については後章で解説します。
クラスの継承による機能追加
ここに鉛筆があるとします。鉛筆には芯があり、書くたびにどんどん小さくなっていきます。これをクラスにしてみましょう。
class Pencil { private: SIntN core; // 鉛筆の芯の長さ public: Void Write() { core -= 1; // 書くたびに芯が 1mm ずつ小さくなる if (core < 0) core = 0; } };
あるとき、鉛筆の後ろに消しゴムをくっつけた新製品が売り出されました。消しゴム付き鉛筆です。消しゴム付き鉛筆は、鉛筆と同じ機能をもちながら、さらに消しゴムの機能ももっています。
このように機能が追加されたクラスは次のように定義することができます。
class ErasePencil : public Pencil { private: SIntN rubber; // 消しゴムの大きさ public: Void Erase() { rubber -= 1; // 消すたびにゴムが 1 立方mm ずつ小さくなる if (rubber < 0) rubber = 0; } };
class キーワードに : public Pencil を指定することで、Pencil のメンバ変数とメンバ関数を引継ぐことができます。つまり、ErasePencil はメンバ変数 core とメンバ関数 Write() をもっています。それに加えて、新しく定義したメンバ変数 rubber とメンバ関数 Erase() ももっています。したがって、次のようなコードを書くことができます。
ErasePencil pen; pen.Write(); // Pencil クラスから引き継いだメンバ関数の呼び出し pen.Erase(); // ErasePencil クラスで新たに定義したメンバ関数の呼び出し
このように、既存のクラスのメンバを引き継ぐことを継承といいます。引き継ぎの元となるクラスを基底クラス、引き継いで新しく定義したクラスのことを派生クラスといいます。「ErasePencil は Pencil の派生クラスである」とか「Pencil は ErasePencil の基底クラスである」という言い方をします。
protected メンバ
鉛筆付き消しゴムはさらに改良されました。消しゴムで消すということは、一度書くのに使われた鉛筆の芯が無駄になるということ。だから、消しゴムで消し取った鉛筆の芯を再利用すれば、芯の減り方が少なくて済むではないか。
こうして新しい製品が開発されました。これは「伸びる鉛筆」という名前で売り出されました。鉛筆の後ろにくっついている消しゴムで消すと、消し取った芯が鉛筆の芯に追加されて、どんどん長い鉛筆になっていくのです!
この伸びる鉛筆をクラスにすると、次のようになります。
class GrowPencil : public Pencil { private: SIntN rubber; // 消しゴムの大きさ public: Void Erase() { rubber -= 1; // 消すたびにゴムが 1 立方mm ずつ小さくなる if (rubber < 0) rubber = 0; core += 1; // 消すたびに芯が 1mm ずつ伸びていく } };
しかし、このクラスは正しく動作しません (コンパイル エラーになります)。なぜなら、メンバ変数 core は Pencil クラスで定義されており、GrowPencil がそのメンバ変数を引き継いではいますが、core は private メンバ変数なので、Pencil クラスの外部からアクセスできないのです。
このような問題を解消するために、protected アクセス制限があります。private メンバはそのメンバを宣言したクラスからしかアクセスできませんが、protected メンバはそのメンバを宣言したクラスだけでなく、その派生クラスからもアクセスできます。
つまり、Pencil クラスを次のように定義しておけば、上記の GrowPencil クラスの定義はエラーになりません。
class Pencil { protected: SIntN core; // 鉛筆の芯の長さ ... };
派生クラスのオブジェクトの型
ErasePencil クラスはPencil クラスのメンバをすべて引き継いでいますから、ErapsePencil のオブジェクトを Pencil のオブジェクトとして扱うことができます。
Void WriteWithPencil(Pencil pen) { pen.Write(); } Void MyFunction() { ErasePencil epen; GrowPencil gpen; WriteWithPencil(epen); WriteWithPencil(gpen); }
このように、「派生クラスのオブジェクトは基底クラスのオブジェクトでもある」ということができます。上記の例ですと、「epenオブジェクトは ErasePencil 型であると同時に Pencil 型でもある」ということができます。
この章のまとめ
- クラスを継承することで、 既存のクラスのメンバを引き継いだ形で新しいクラスを定義できます。
- private メンバは派生クラスからアクセスすることはできませんが、 protected メンバは派生クラスからアクセスすることができます。
- 派生クラスのオブジェクトは基底クラスのオブジェクトでもあります。
やってみよう
次のようなコードを実行してみましょう!
Write() メンバ関数が引き継がれていることを確認しましょう。Pencil クラスの core メンバ変数を privateにするとどうなるでしょうか? どんなエラーがでるでしょうか?
Void TestInherit(Void) { Pencil pen; ErasePencil epen; GrowPencil gpen; pen.Write(); epen.Write(); epen.Erase(); gpen.Write(); gpen.Erase(); }