C++ 再入門 その16 - 親クラスのオーバーライドとアクセス指定子
さて、前回、あるクラスを「継承」して、継承元のクラスが持っている関数を使って、その「違い」だけを書くことで新しいクラスを作る例を見てきました。
C++ 再入門 その15 - 継承とは何か
継承する「親クラス」(または基底クラス)と、継承した「子クラス」(派生クラス)の関係をもう少し丁寧に確認してみましょう。
#include <iostream>
#include <string>
using namespace std;
// 親クラス
class Object {
public:
Object(string name) { m_name = name; }
string name() { return m_name; }
private:
strig m_name;
};
// 子クラス
class ColorObject : public Object {
public:
ColorObject(string name, string color) : Object(name) { m_color = color; }
string color() { return m_color; }
private:
string m_color;
};
int main() {
// 親クラスを使う
Object o = Object("obj");
cout << o.name() << endl;
// 子クラスを使う
ColorObject co = ColorObject("whiteObj", "white");
cout << co.name() << ":" << co.color() << endl;
}
まず、public と praive の復習ですが、親クラスの public に書かれている関数や変数は、このクラスの関数で呼び出したり読み書きすることが出来ますし、クラスのインスタンス変数からも呼んだりアクセスすることが出来ます。ところが private に書かれている関数や変数は、このクラスの関数で呼び出したり読み書きは出来ますが、インスタンスからは「見えない」ので、呼び出したり読み書きすることは出来ません。
さて、クラスを継承した子クラスからも、インスタンスと同じで、public に書かれているものを使うことは出来ますが、private に書かれているものは呼び出したり、読み書きすることは出来ません。インスタンスと同じ立場です。そして子クラスのインスタンス変数からは、やはり public に書かれているものだけが使え、private にあるものは見えません。但し子クラスのインスタンスからは子クラスだけではなく親クラスの public にもアクセスすることが出来るのがポイントです。
親クラスのインスタンスおよび子クラスからは、親クラスの public にあるものが見える。
子クラスのインスタンスからは、親クラスと子クラスの public にあるものが見える。
さて、ここまでは良いですね。ところで子クラスの中に親クラスと全く同じ名前(プロトタイプ)のものがあるとどうなるのでしょう。
...
// 子クラス
class ColorObject : public Object {
public:
ColorObject(string name, string color) : Object(name) { m_color = color; }
string color() { return m_color; }
string name() { return “*”; } // これを追加
private:
string m_color;
};
...
親クラスの name() と同じメソッドを子クラスに定義してもエラーにはなりません。親クラスのメソッドは「オーバーライド」されて、子クラスのインスタンスから、このメソッドを呼び出せば、それは親クラスのメソッドではなく子クラスのメソッドが呼ばれます。
メソッドが子クラスでオーバーライドされている場合に、親クラスのメソッドをどうしても呼びだければ、
ColorObject co = ColorObject("whiteObj", "white");
cout << co.Object::name() << ":" << co.color() << endl;
と基底クラスのメソッドであることを明示すれば呼び出すことは出来ます。publicに書かれている以上、オーバーライドしても隠れるだけで見えなくなるわけでは無いのですね。これは子クラスからさらに継承して孫クラス、さらにひ孫クラスを作っても話は同じです。このように子クラスは親クラスのインスタンスと同じ扱いで、子供であるからと言って特別扱いをしてくれるわけではないのです。一般的にprivateにはクラスの中だけで使う状態を覚えておく変数や、外部からは関係のない実装上に必要な関数を書くのですが、子クラスの実装上の都合で親クラスの変数にどうしてもアクセスしたいであるとかいうことはママあります。例えば値を読み出すメソッドはpublicになっているけど、子クラスからは値を変更したいので子クラスから「のみ」書き込むメソッドにアクセスしたいなんていうこともあります。このように子クラスからは見えるけどインスタンスからは見えない変数やメソッドを書きたければ、public や private ではなく、protected という場所を用意して、そこに書くのです。
まだ説明していないことも多いので、具体的な例を出すのに詰まってしまったのですが、ちょっとズルをして例を作ってみました(本当はいろんなところに const を付けたい^^;)。
#include <iostream>
#include <string>
using namespace std;
static int max_id = 1;
class Object {
public:
Object(string name) { m_name = name; m_id = max_id; max_id++; }
string name() { return m_name; }
protected:
int id() { return m_id; }
private:
string m_name;
int m_id;
};
class ColorObject : public Object {
public:
ColorObject(string name, string color) : Object(name) { m_color = color; }
string color() { return m_color + "(" + to_string(this->id()) + ")"; }
private:
string m_color;
};
int main() {
Object o = Object("obj");
cout << o.name() << endl;
ColorObject co = ColorObject("whiteObj", "white");
cout << co.name() << ":" << co.color() << endl;
}
Objectに名前(name)を付けるにしても、同じ名前を付けることが出来る仕様なので、その区別を付けるためにidを振ることにしました。ズルなのは普通のstaticなグローバル変数で値を用意したことで、本当であればこれは隠蔽したほうが良い値です。Objectをコンストラクトした時にグローバル変数を使ってidを振ります。これはあくまで実装の都合ということで、子クラスからのみ使えるように protected の場所にメンバ関数を書きました。子クラスのcolorメソッドを使って色を取り出す時に、親クラスのidを含んだ文字列を返すようにしました。もちろん親クラスや子クラスのインスタンスからはidを取得することは出来ません。protected に書かれたメンバは、子クラスなど特別なところで「のみ」使うことができます。
なお、color() の中で protected なメンバ関数を呼び出す時に this を使っていますが、自身の持つメンバ関数を呼び出す時には普通の関数と区別するために、このような書き方をするのだととりあえずはスルーしておいてください(これも必ずしも this 使えるわけではないので、いろいろと面倒)。
さて、ややこしいのは protected に書かれたものを継承した時に、そのまま protected になるわけではないことです。孫クラスを作った時に、お爺さんの protected は見えるの?というアタリから面倒なことになったりします。その辺りからは次回ということで。
ヘッダ画像は、以下のものを使わせていただきました。https://commons.wikimedia.org/wiki/File:ISO_C%2B%2B_Logo.svg
Jeremy Kratz - https://github.com/isocpp/logos , パブリック・ドメイン,
https://commons.wikimedia.org/w/index.php?curid=62851110による
#プログラミング #プログラミング言語 #プログラミング講座 #CPP #クラス #継承 #オーバーライド #public #private #protected #アクセス指定子 #thisポインタ