コンパイルが止まらない

なんか珍しいものを見た.TopCoderではコンパイルに30秒以上かかっちゃダメらしい.


コンパイルができないと何の情報も無いまま目だけでデバッグしなくちゃならないので精神的にとても辛い.

どうやら原因はでかいメンバ変数のようです.

#include<vector>
class C {
  std::vector<int> a[10000000];
};

上のコードをコンパイルするとずんずんメモリを喰っていきます.
実行時でなくコンパイル時に負荷がかかるというのは,おそらくクラスのサイズ決定時にテンプレートが絡んでほげほげだろうとは思うのですが詳しくは謎.

ともかく,こういうでかい変数は原則としてグローバルか static に取りましょうってことですね.もう嫌だこの言語.

カリー化デコレータ

Python のデコレータはなかなか面白い機能だと思ったので、試しにカリー化を実装してみました。

@curry
def f(x,y,z):
    ...

などと書くと、カリー化されたfが定義されます。まぁ functools.partial があれば全然必要無いシロモノではあるのですが。
複数まとめて関数適用するとき f(x)(y)(z) とか気持ち悪いので、f.apply(x,y,z) みたいに普通っぽく呼び出せるようにもなっています。
可変長引数とは相性悪いです(*付き引数=引数1個ぶんとして扱われます)。

def arity_of(f):
    return len(f.func_code.co_varnames)

class curry:
    def __init__(self, f, n=None, args=None):
        self.func = f
        self.arity = n if n != None else arity_of(f)
        self.args = args   # linked list

    def __call__(self, x):
        f = curry(self.func, self.arity-1, (x, self.args))
        if f.arity <= 0:
            return f.__apply_args()
        else:
            return f

    def __apply_args(self):
        args = []
        l = self.args
        while l != None:
            args.append(l[0])
            l = l[1]
        return self.func(*reversed(args))

    def apply(self, *args):
        f = self
        for x in args:
            f = f(x)
        return f


if __name__=='__main__':
    @curry
    def test(x, y, z):
        print x, y, z
        return x + y * z

    print test(1)(2)(3)
    print test.apply(1,2,3)
    f = test(1)
    g = test.apply(1,2)
    print f.apply(2,3)
    print g(3)

考えてみてくださいとかいいつつ

コレ

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

自分でてきとうに書いてみました。 Class.getDeclaredMethod() が微妙に使えない子だった(シグネチャが引数に完全一致するメソッドしか取ってこれない)。

一応実行時に型チェックしつつ、protectedメソッドにもちゃんとアクセスできます。プリミティブ型の扱いがぞんざいなのはしょうがない。Javaだし。
多重継承か mixin が欲しくなる感じのアレです。

import java.lang.reflect.Method;

public class MethodSeek {
    public static Method getMethod(Object obj, String mname, Object... args)
        throws NoSuchMethodException {
        Class klass = obj.getClass();
        Method[] methods = klass.getDeclaredMethods();
        for (Method m : methods) {
            if (m.getName().equals(mname)) {
                if (typeCheck(m.getParameterTypes(), args)) {
                    return m;
                }
            }
        }
        throw new NoSuchMethodException(klass.getName() + "." + mname + "()");
    }
    protected static boolean typeCheck(Class[] types, Object[] args) {
        if (types.length != args.length) return false;
        for (int i = 0; i < types.length; i++) {
            if (!(types[i].isInstance(args[i]))) return false;
        }
        return true;
    }
}

こんなかんじで HogeX を定義できます。

import java.lang.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

class Hoge {
    private int a;
    public Hoge(int a) { this.a = a; }
    protected Integer prot(Integer b, Integer c) {
        System.out.println("Hoge:protected");
        return a + b + c;
    }
}

class HogeX extends Hoge {
    public HogeX(int a) { super(a); }
    public static <T> T invoke(Object obj, String mname, Object... args)
        throws IllegalAccessException,
               InvocationTargetException,
               NoSuchMethodException {
        Method m = MethodSeek.getMethod(obj, mname, args);
        return (T)(m.invoke(obj, args));
    }
}

class Test1 {
    public static void main(String[] args) {
        try {
            Hoge x = new Hoge(100);
            Integer result = HogeX.invoke(x, "prot", 30, 4);
            System.out.println(result);
            result = HogeX.invoke(x, "prot", 1, 2, 3);
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

実行結果

Hoge:protected
134
java.lang.NoSuchMethodException: Hoge.prot()
	at MethodSeek.getMethod(MethodSeek.java:14)
	at HogeX.invoke(test1.java:20)
	at Test1.main(test1.java:31)

関数ポインタ

全部わかったらえらいです。

#include <stdio.h>
void f(){}
int main() {
  void (*p)() = f;
  printf("%x, %d\n%x, %d\n%x, %d\n%x, %d\n%x, %d\n",
         &f, sizeof(&f),
         f , sizeof f,
         *f, sizeof(*f),
         p , sizeof p,
         *p, sizeof(*p));
  return 0;
}

実行結果: C C++

=f は型的には =&f なのですね。
*は何個くっつけても変わらんぽい。***f とか ******p とか。
サイズ 1 になってるやつはCでは未定義かな。

アクセス制御の抜け穴 (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フィールドが無ければできますが

アクセス制御の抜け穴 (OCaml編)

Obj.magic 最強、で終わらそうと思ったのですがそう単純な話でもないらしい。
まず OCaml の private メソッドは、Javaなどのprotected相当で、継承した先のクラスからアクセスすることができます。
しかし呼び出せるのは自分に対してだけで、引数として受け取ったオブジェクトに対して呼び出すといったことはできません。method f x = x#g などと定義すると、fの引数xには"gというpublicメソッドを持った型"しかつけられなくなります。

class hoge = object
  method private priv = print_endline "hoge:private"
end
class fuga = object
  inherit hoge as super     (* hogeを継承 *)
  method priv = super#priv  (* 同名のメソッドをpublicに再定義可能 *)
  method call_priv (x:hoge) = x#priv  (* type error *)
end
    method call_priv (x:hoge) = x#priv
                                ^
This expression has type hoge
It has no method priv

また、private メソッドは、class type を制限することで継承先クラスにも見せない、本当にそのクラスだけのためのメソッドにすることができます (モジュールのシグネチャを制限するのと同じような感じ)。

class hoge : object
  (* priv はない *)
end = object
  method private priv = print_endline "hoge:private"
end
class fuga = object
  inherit hoge as super
  (* priv は使えない *)
  method func = super#priv; print_endline "fuga:func"
end
    method func = super#priv
                  ^^^^^
This expression has no method priv

で、メソッドがprivateかどうかってのは純粋に型の問題でコンパイラさえごまかしてしまえばなんとかなりそうだと思ったので Obj.magic でいじれば十分だろうと考えたのですが、うまくいったりいかなかったり。内部表現どうなってのコレ。

class type visible = object
  method priv : unit    (* public *)
end
class hoge = object
  method private priv = print_endline "hoge:private"
end

let _ =
  let x = new hoge in
  let y:visible = Obj.magic x in
  y#priv
hoge:private

これはうまくいく。

class type restricted = object
  (* empty *)
end
class type visible = object
  method hidden : unit
end
class hoge : restricted = object
  method private hidden = print_endline "hoge:hidden"
end

let _ =
  let x = new hoge in
  let y:visible = Obj.magic x in
  y#hidden
hoge:hidden

これもうまくいく。しかし

class type visible = object
  method priv1 : unit
  method priv2 : unit
end
class hoge = object
  method private priv1 = print_endline "hoge:private 1"
  method private priv2 = print_endline "hoge:private 2"
end

let _ =
  let x = new hoge in
  let y:visible = Obj.magic x in
  y#priv1;
  y#priv2
hoge:private 2
hoge:private 2

二つ以上になるとうまくいかない!?

うまくいかない場合について、もう少し厳密に調べたい。