既にあるクラスの定義を内包するようなクラスを作成することを、クラスの継承といいます。元のクラスをベースクラスとか基本クラスとか親クラスなどと呼び、新しく出来上がったクラスをディライブドクラスとか派生クラスとか子クラスなどと呼びます。クラスを継承すること自体を、サブクラス化とよぶこともあります。これらは、すべて同じことを異なる言葉で表しているだけです。このページでは、元のクラスを基本クラスと呼び、基本クラスを継承してできたクラスを派生クラスと呼ぶことにします。
最も簡単なクラスを、もっとも単純に継承すると、次のようになります。
class CManiac {}; // 最も単純な基本クラスの定義 // 派生クラスの定義 class CManiacInherited : public CManiac {};
青で強調したところが、継承に係わる部分です。クラスの継承をするときには、このように、基本クラスの名前を : public CManiac と書いた部分のようにクラス定義に追加します。これで、基本クラス CManiac から CManiacInherited を派生させた、事になります。
ナオ : でっ、これで、何ができるの?
店主 : まぁまぁ、あわてなさんな。すぐに分かるから。
ここで、public って何? ということになりますが。これは、派生クラスのユーザに対して、基本クラスのメンバへのアクセスの追加制限を行わないという指定です。つまり、アクセスは、基本クラスで定義されているメンバの public、protedted、private の別に従って行われます。
基本クラスのメンバへのアクセスの追加制限を行う場合には、上記の public に代えて protected または private を使います。このような場合には、派生クラスのユーザに対しては、基本クラスのメンバの public、protected、private の指定と比較して、厳しいほうの制限が行われます。アクセス制限は public < protected < private の順に厳しくなります。このようにして決まったアクセス制限について、派生クラスのユーザは、次の表のようにメンバにアクセスできます。
厳しい方のアクセス制限が | 派生クラスのユーザは、基本クラスのメンバに |
---|---|
public の場合 | アクセスできる |
protedted の場合 | アクセスできない |
private の場合 | アクセスできない |
上記は、クラスのユーザに対するアクセス制御でしたが、派生クラスの作者については、基本クラスで定義されているアクセス制御そのものが適用されます。つまり、public、protected、privateのどれを書いても、クラスの作者は基本クラスに対する自分自身のアクセス権を制限することにはならないわけです。基本クラスのアクセス制限 public、protected、private によって、クラスの作者は、次の表に従って、メンバにアクセスできます。
基本クラスのアクセス制限が | 派生クラスの作者は、基本クラスのメンバに |
---|---|
public の場合 | アクセスできる |
protedted の場合 | アクセスできる |
private の場合 | アクセスできない |
ちょっと、具体例をみてみましょう。まずクラスの作者の場合、基本クラスの public だけが関係します。
class CManiac { public: int m_iValue; }; // 派生クラスの定義 class CManiacInherited : protected CManiac { public: void function() { int i = m_iValue; // 派生クラスの作者が、基本クラスのメンバにアクセスする場合、public のみが関係する。 // 自分で書いた protected によってアクセスを制限されることは無い。 } };
派生クラスのユーザの場合はどうなるかというと、次のように CManiacInherited のオブジェクトからは、protected、public の厳しいほうである protected が適用されるので、m_iVaue にはアクセスできません。アクセスできないとは、コンパイルエラーになってしまうという意味です。
int main() { CManiacInherited maniacinherited; maniacinherited.m_iValue; // コンパイルエラーになる。ユーザからは protected なメンバは使えない。 }
上記のように、クラスの継承時には、基本クラスのアクセス制御と、派生クラス定義でのアクセス制御の厳しいほうが、クラスのユーザに適用されるというわけです。これによって、派生クラスの作者は、自分には見えているけれども、ユーザには使わせたくない基本クラスのメンバを隠すことができるようになっているわけです。
ナオ : 派生クラスのユーザへのアクセス制御は、厳しい方が適用されるってわけね。
店主 : ですな。次は、ちょっと具体例でも出しましょうかな、お嬢ちゃん。
ナオ : ハイハイっ、そうしてっ。
では、ここで、クラスの継承の具体例を見てみましょう。C 言語や C++ のライブラリには、printf という関数がありますね。この関数は、標準出力に文字列をフォーマットして出力してくれる関数です。sprintf という関数もあって、これは、文字列をメモリに書くようになっています。
C++ マニアック的には、メモリに書いてくれるのではなく、STL の string を返してくれる方が嬉しい、というわけで、そのようなクラス Sprintf を string を基本クラスとして定義してみましょう。
#include <string> #include <cstdarg> using namespace std; struct Sprintf : public string { Sprintf(const char* pcFormat, ...) { va_list valist; va_start(valist, pcFormat); char acTable[5000]; vsprintf(acTable, pcFormat, valist); assign(acTable); // 基本クラス string の関数 va_end(valist); } };
Sprintf は、簡単に、クラスではなく、構造体(struct)としました。C++ の構造体は、中身がすべて public なクラスであると考えてください。これで、Sprintf は string でもあるので、次のように、標準出力 cout に直接出力することができます。つまり、クラスの継承によって printf と同様のフォーマット機能を持った string が出来上がったというわけです。
#include <iostream> using namespace std; int main() { cout << Sprintf("How maniac = %d", 1000) << endl; return 0; }
ナオ : マニピュレータの作り方でも同じようなことやってたっしょ。でも、この Sprintf の方がスマートかもね。
店主 : まぁね。ひとつのことをするための方法は、ひとつだけじゃないってことですな。
... to be continued!