ホーム < ゲームつくろー! < C++踏み込み編


その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メンバにだけアクセスできます。

 これは、当然と言えば当然です。親クラスの実装がライブラリ化などで隠蔽されているとして、派生したその内部クラスが親クラスにアクセス出来たら、カプセル化が破壊されてしまいます。ん〜、わかりますけどちょっと残念(^-^;。