その13 内部クラスは外側クラスのメンバにアクセスし放題!
クラスはメンバ変数や関数へのアクセス権を細かく設定出来ます。これによりメンバ変数のカプセル化がはかれ、クラス機能の不正な改変を防ぐ事ができます。例えばメンバ変数をprivateとして宣言すれば、そのクラス内部意外ではアクセスができなくなります。
そのアクセス権限の壁を完璧に超えられる人がいます。それは「自分自身」です。当たり前ですね。ところが、なんと、自分以外にもう一人完全に超えられる人がいます。それはクラスの中で定義されるクラス、つまり内部クラス(インナークラス:Inner Class)です。内部クラスとは例えば次のように宣言されます:
class Parent {
// 内部クラス
class Child {
int val;
public:
Child();
~Child();
void setVal(int v);
};
public:
Parent();
~Parent();
};
クラスの中にクラスが宣言されているのがわかりますね。この内部クラスの実装は次のようになります:
void Parent::Child::setVal(int v) {
val = v;
}
親の中にいるのでスコープが連鎖しているわけです。孫クラスがあったとしても、これと同様のスコープ解決をしていくだけです。
さて、内部クラスの何が面白いのか?それは「内部クラスにしてみると、親クラスのメンバがグローバル」であるように見える事にあります。次のサンプルを御覧下さい:
#include <stdio.h>
class MyMain {
class Inner {
public:
void update(MyMain &main) {
main.val += 1;
}
};
private:
int val;
Inner inner;
public:
MyMain() : val() {}
bool update() {
inner.update(*this);
return true;
}
};
int main() {
MyMain myMain;
// アプリケーションループ
while(myMain.update());
return 0;
}
何をしているか説明します。MyMainクラスはメイン関数のような役割をしています。毎フレームupdateメソッドを呼ぶ事で時間が少しずつ進むゲームのメイン関数のような働きです。MyMainクラスの中にはInner内部クラスが定義されています。またその実体も持っています。MyMain::updateメソッド内ではInnerオブジェクトのupdateメソッドが呼ばれています。Inner::updateメソッドの内部がポイントです。引数に渡されたMyMainクラスが持っているprivateなvalメンバに直接アクセスできてしまっています!つまり、Innerクラスにしてみると、引数のMyMainオブジェクトのメンバが自分の知っているグローバル変数のような感覚になっているわけです。
MyClassのvalメンバ変数は、もちろん外の人からは一切見えません。でも、内部の人はアクセス自由。これは「グローバル変数のスコープ化」をしている状態です。この感覚は大切です。変数を極力さわらせないのがオブジェクト指向の基本です。完全なグローバル変数はそれに逆行しているのでまずい。シングルトンオブジェクトもヘッダーをインクルードすると誰でも触れてしまうので良くはない。でも上のように外側をクラスでくくり、それにアクセスするクラスを内部クラスとして作ると、グローバル変数のスコープ化ができます。
もちろん、必ずこうしなければならないわけではありません。内部クラスを延々と何百も作っていくのは明らかにおかしいです。時と場合を見計らって賢く使っていきましょう。
@ 派生クラスはもうアクセス無理!
さて、内部クラスは外側クラスのメンバにアクセスし放題です。では、内部クラスの派生クラスはどうか?
#include <stdio.h>
class MyMain {
protected:
class Inner {
public:
virtual void update(MyMain &main) {
main.val += 1;
}
};
private:
int val;
Inner inner;
public:
MyMain() : val() {}
bool update() {
inner.update(*this);
return true;
}
};
class MyMainEx : public MyMain {
protected:
class InnerEx : public Inner {
virtual void update(MyMain &main) {
main.val += 1;
}
};
};
MyMainExはMyMainを親に持つ派生クラスです。その内部で内部クラスも派生しています。InnerクラスはMyMainにアクセスできるのだから、InnerExクラスも出来そうに思いますが、実はこれはできません!外側クラスのメンバにアクセス出来るのは、あくまでも「そのクラス内で宣言されたクラスのみ」なんです。InnerExクラスはMyClassExクラスで宣言されているのでMyClassは別人、つまりprivateメンバにはアクセスできません。もちろんprotectedにしてもだめです。publicメンバにだけアクセスできます。
これは、当然と言えば当然です。親クラスの実装がライブラリ化などで隠蔽されているとして、派生したその内部クラスが親クラスにアクセス出来たら、カプセル化が破壊されてしまいます。ん〜、わかりますけどちょっと残念(^-^;。