アクセス制御の抜け穴 (C++/Java ; protected編)

さて C++Java です。
privateへのアクセスはひとまず後回しにするとして、まずは protected なところから考えていきましょう。

C++ の場合 (protected)

欲しいメソッドがサブクラスからアクセス可能なので、自分でサブクラスを定義することで簡単に手に入ります。

Hoge クラスのオブジェクト x に対して prot() メソッドを呼び出したいというシチュを考えましょう。

class Hoge {
public:
  Hoge(): a(0) {}
  void p() { cout << a << endl; }
protected:
  void prot(int b) { a = b; }
private:
  int a;
};
int main() {
  Hoge x;
  x.prot(3);  // error
}

C++ではサブクラスで同名のメソッドを public に再定義可能です。(これができないオブジェクト指向言語ってあるのだろうか?)
アクセス指定のみ変更したサブクラスを作り、参照やポインタをダウンキャストしてやるとそのまま使えます。(これが安全でないこととかあるのだろうか?)
Hogeクラス自体が継承されることを想定されていない(メソッドにvirtualが指定されていない)場合はHogeXオブジェクトを作るのはまずいのでコンストラクタをprivateにしてあります。

class HogeX : public Hoge {
public:
  void prot(int b) { Hoge::prot(b); }
private:
  HogeX(){}
};
int main() {
  Hoge x;     x.p();
  HogeX &y (static_cast<HogeX &>(x)); // downcast
  y.prot(3);  x.p();
}

このようなサブクラスを定義してやれば、xを使う側は「protected メソッドが public になった x 」を得られるようになります。
ただし protected なメンバ変数へアクセスするには、getter/setter をサブクラスで別途用意してやる必要があります。

また、static な protected メソッドも、HogeX経由で public にしてしまうことができます。

Java の場合 (protected)

同一パッケージ内からは普通にアクセスできますので、別パッケージからのアクセスを考えます。
Java だと上のようなことはできません。自分の実行時の型より下へのダウンキャストは全て例外になるからです。次のコードはコンパイルは通りますが実行すると例外を吐いて死にます。

class Hoge {
  protected void prot() { System.out.println("Hoge:protected"); }
}
class HogeX extends Hoge {
  public void prot() { super.prot(); }
}
class Client {
  public static void main(String[] args) {
    Hoge x = new Hoge();
    HogeX y = (HogeX)x;   // ClassCastException
    y.prot();
  }
}

Javaはキャストに関しては相当保守的で、以下のような自明に安全なダウンキャストすら例外を吐いてしまいます。

class Hoge2 extends Hoge {}  // 何もしない!
class Client {
  public static void main(String[] args) {
    Hoge x = new Hoge();
    Hoge y = (Hoge)((Hoge2)x);   // 結局同じ型!
  }
}

どうやら「 x から"protected メソッドが public になった x "を得る」ことは難しそう*1なので諦めることにして、次に「xの任意の protected メソッドを実行する」ことを考えましょう。これぐらいならできます。

class HogeX extends Hoge {
  public static void callProt(Hoge x) { x.prot(); }
}
class Client {
  public static void main(String[] args) {
    Hoge x = new Hoge();
    HogeX.callProt(x);
  }
}

もうちょい一般的な形で

result = HogeX.invoke(x, "methodName", param1, param2);

のようなメソッドもリフレクションと可変長引数とジェネリクスを使って作れそうな気がします。(「可変長」と「オーバーロード対処」と「引数の型を明示的に与えなくてよい」を全部見たすのは難しいかも。Java詳しい方は考えてみてください。=>実装例)



―――privateへのアクセスについてはまた後で書きます。

*1:まぁprivateフィールドが無ければできますが