Advertisement

More Related Content

Advertisement

More from Preferred Networks(20)

Advertisement

Pfi Seminar 2010 1 7

  1. Caution!  本発表には次のものは含まれません  C++の有用なプログラミング技法  広くつかわれるべきテクニック  その他なにか視聴者に役立つもの  そういうのを期待された方はあしからず
  2. 自己紹介  田中英行 (@tanakh, id:tanakh)  Haskell愛好家  C++歴  1998年 ~  2005年ぐらいまでは漫然と使っていました  社内用C++ライブラリ(pficommon)を作成  C++のきもさを再認識  その辺で得られた知見を話します ○ ※実際に使われているわけではないです
  3. pficommon  Boostの広く利用できる部分(tr1)  データ構造いろいろ  サーバー書く向け機能  マルチスレッド  各種プロトコル  RPC  ウェブアプリ向け機能  DSL
  4. 本日の内容  ユーザー定義構文  Serializable万能説  Stream Fusion  の三本立て
  5. 概要  C++の構文のように扱える構文を 定義してみようとすると、 それがどういう意味を持つのだろうか、 というお話
  6. ユーザー定義構文  ユーザー定義する組み込み構文のように 扱えるもの  例えば  synchronized (Java)  using (C#)  actor (Scala)  のようなものがC++でも書けると嬉しい ですね
  7. ほかの言語の例  Lisp  マクロ・高階関数  Haskell  高階関数・モナドを引数にとる関数  Ruby  ブロックをとる関数  Scala  最後の引数にラムダ式をとる関数を書くと それっぽく見える
  8. 例:foreach (for-each (lambda (x) forM_ [1..5] $ ¥x -> (display x) print x ‘(1 2 3 4 5)) (1..5).each{|x| List(1,2,3,4,5).foreach{ x => p x println(x) } }
  9. C++でのユーザー定義構文(1)  statementを引数に取るマクロを書き、 組み込みの構文要素に置換する #define foreach(v, c, s) ¥ for(typeof(c.begin()) it=c.begin(); it!=c.end(); it++){ ¥ v = *it; ¥ s ¥ } … vector<int> v; v.push_back(1); … foreach(int x, v, { cout<<x<<endl; })
  10. 問題点  簡単に書けて、扱いやすいが、 かっこ悪い  foreach(, , {}) ↑ 括弧の中に{}があるのが耐えられない }) って… foreach(int x, v) { cout<<x<<endl; }  のように書きたい
  11. C++でのユーザー定義構文(2)  statementが後ろに来て整合性が取れる ようなマクロを書く #define foreach(v, c) ¥ for (bool b=true; b; ) ¥ for (v; b; b=false) ¥ for(typeof(c.begin()) it=c.begin(); it!=c.end() && (v=*it, true); it++) … vector<int> v; v.push_back(1); … foreach(int x, v){ cout<<x<<endl; }
  12. C++でのユーザー定義構文(2)  この書き方は美しいが、 記述力を制限される  既存の制御構文の後ろに文を置けるだけ
  13. 後処理  synchronized(v){ … } のようなことを実 現するには、{ … } の後に処理をさせな ければならない
  14. forを使う方法  後続の文より後に処理をさせる方法  forを使う #define synchronized(v) ¥ for (bool b=true; ¥ b && (v.lock(), true); ¥ b=false, v.unlock()) #define synchronized(v) ¥ for (bool b=true; b; ) ¥ for (scoped_lock lk(v); b; b=false)  break, continueが食われてしまう
  15. ifを使う方法  ifの中で変数を宣言すれば、 後続の文の後にデストラクタを動かせる #define synchronized(v) ¥ if (scoped_lock lk=scoped_lock(v))
  16. ifを使う方法(2)  コピー可能なクラスを定義する  trueを返すoperator bool() を定義する  デストラクタを定義する class hoge { … }; if (hoge h=hoge()) stat; 処理の流れ  hoge::hoge()  hoge::operator bool()  stat  hoge::~hoge()
  17. 改良  operator bool() はfalseを返したほうがいい #define synchronized(v) ¥ if (scoped_lock lk=scoped_lock(v)); ¥ else
  18. 活用例:CGI DSL class my_cgi : public cgi{ public: void run(){ html__{ head__{ title__{ text__("タイトル"); } } body__{ a__{ href__ = "http://kzk9.net/blog/"; text__("super blog!"); } br__; } } } };
  19. 議論  hoge(…){ … } の形で、 前処理→処理→後処理 の形のユーザー 定義構文が書けることが分かった  だがそれだけだろうか?  こういうことができるということは、 つまりどういうことなのだろうか
  20. 見えざる継続  継続とは  ある計算過程のある瞬間における、その過 程の未来全体を表すもの、あるいは計算過 程の実行スナップショットと説明される。 (Wikipedia)  Schemeなどでは言語レベルでサポート  継続は、実行中のどんな計算機プログラ ムにも存在する
  21. 関数呼び出しと継続  関数呼び出しとはすなわち、継続をとも なう手続きへのジャンプ  関数が呼ばれた= 関数から返った後の継続が手に入る int foo(){ return 1; } foo()が呼ばれる際に渡される継続 ・返された値に2を書けて void bar() ・それを表示する { cout<<foo()*2<<endl; 実際のところC++では、 } ・スタック ・リターンアドレス の対として表現される
  22. ユーザー定義構文では  hoge::operator bool() が呼ばれた瞬間の 継続に注目する class hoge { … }; if (hoge h=hoge()) stat; 処理の流れ  hoge::hoge()  hoge::operator bool() ← trueを返せば stat → hoge::~hoge() → …  stat falseを返せば hoge::~hoge() → …  hoge::~hoge() なる継続
  23. 継続を取り出す  callee save レジスタ、スタック、リター ンアドレスを取り出す cont get_ret_cont(){ regs=get_callee_save_regs(); stack=get_stack(); ret=get_ret_addr(); return (regs, stack, ret); }
  24. 継続が取り出せるなら  operator bool()にて、継続を取り出し、 さらに文実行後の継続を設定し、stat後 の処理の流れを決められる hoge::operator bool() { cont c=get_ret_cont(); next=…; c(true); } hoge::~hoge() { next(); }
  25. 文を後ろに置く=継続を渡す  つまるところ、if (hoge h=hoge()) { … } は 後ろの文を引数に関数を呼び出しているの と同じである  Schemeの高階関数や、Rubyのブロック構 文と同じことができる
  26. 例:スレッド  thread{ … } でスレッドを立てる 後続の文が終了するとスレッド終了 mutex m; int n; int main() { thread{ for (int i=0; i<10; i++) synchronized(m) n++; } for (int i=0; i<10; i++) synchronized(m) n--; }
  27. 実装 class thread_forker{ … }; thread_forker::operator bool(){ cont c=get_ret_cont(); pthread_create(&tid, NULL, bind(apply_cont, c, true)); c(false); } thread_forker::~thread_forker(){ pthread_exit(NULL); }
  28. 別スレッドでの継続の起動  スタックを別のスタックにコピーする  ポインタの張り替えなどめんどい ret ret ret frame frame frame ret ret ret frame frame frame
  29. まとめ  C++でユーザー定義構文は意外ときれい に作れるんじゃなかろうか  ポータブルな実装ができるかどうか
  30. 概要  シリアライザというものがあります  シリアライザが意外と色々なところに使 えるという話
  31. シリアライザとは  データをバイト列に変換したり(シリアラ イズ)、バイト列からデータに変換したり (デシリアライズ)するもの  クラスやコンテナの内容をファイルに保存 したり、ネットワーク越しに転送したりす るのにとても便利
  32. boost::serialization  Boostに入っているシリアライズライブ ラリ class hoge{ private: string a; vector<int> b; friend class boost::serialization::access; template <class Archive> void serialize(Archive &ar){ ar & a & b; } };
  33. boost::serializationの特徴  シリアライズとデシリアライズを 共通のコードで記述  テンプレートでディスパッチ hoge h; text_oarchive oa(cout); oa << h; // text_oarchiveを引数にserializeが呼ばれる hoge g; text_iarchive ia(cin); ia >> h; // text_iarchiveを引数にserializeが呼ばれる
  34. 仕組み  serialize()関数をオーバーロード template <class Archive> Archive &operator &(Archive &ar, T &v){ serialize(ar, v); return ar; } void serialize(text_iarchive &ar, int n){ ar.read_int(n); } void serialize(text_oarchive &ar, int n){ ar.write_int(n); }
  35. シリアライズするというのはどうい うことか?  クラスのシリアライズ  メンバの列挙  コンテナのシリアライズ  データの列挙
  36. テンプレートでのシリアライズ  serializeがテンプレートメンバ関数  シリアライズ・デシリアライズを 共通化  色々なシリアライザ(text, binary)に適用 可能  拡張可能  → シリアライザじゃなくてもいいので は?
  37. 型を書きだす  データの代わりに、型を書きだしてみる class type_oarchive{ … }; template <class T> void serialize(type_oarchive &oa, T &v){ // デフォルト oa.enter_struct(); serialize(oa, v); oa.leave_struct(); } void serialize(type_oarchive &oa, int &n){ // 特殊化 oa.add(new int_type(true, sizeof(int))); } …
  38. 型を書きだす(2)  コンテナ型は特殊化する template <class T> void serialize(type_oarchive &oa, vector<T> &v){ oa.add(new array_type(get_type<T>()); } template <class K, class V> void serialize(type_oarchive &oa, map<K, V> &v){ oa.add(new map_type(get_type<K>(), get_type<V>()); }
  39. 型を書きだす(3)  型情報取得関数 template <class T> type_info *get_type() { T v; type_oarchive oa; oa << v; return oa.get(); }
  40. Serializable = Reflectable  値の代わりに型を書きだすことにより、 Serializableなクラスは(部分的には) Reflectableなクラスとなる  Reflectableなクラスは明らかに Serializableに出来るのでこれはそう おかしな話ではない  つまり、(部分的には)Serializableと Reflectableは等価である
  41. 応用例:RPC // シグニチャ RPC_PROC(add, int(int, int)) // メソッド定義 RPC_GEN(calc, add) // クラス定義 // サーバ // クライアント int add(int x, int y){ return x+y; } hoge_client cli(“localhost”, 12345); int main(){ cout<<cli.call_add(1,2)<<endl; hoge_server serv; // ↑ 3が返ってくるはず serv.set_add(&add); serv.serv(12345, 10); }
  42. RPC説明  適当に関数のシグニチャを定義して、 それに合う関数をセットする  関数の引数、返り値はソケットでやり取 りされる → シリアライズ可能でなけれ ばならない
  43. RPC:クライアントコード生成  言語bindingを自動で生成  C++ソースを読み込む  パーズするのは大変すぎる  g++にやらせる  リフレクションする  RPCの型情報が取れる  好きなコード生成できる  RPCでやり取りするデータはすべてSerializable でなければならないはずなので、すべて何もし なくてもリフレクション出来るはずである for
  44. RPC:テスト生成  おなじ原理でRPCテスト用のWebサー バーをC++コードから自動で生成
  45. まとめ  Serializableなデータ構造は思ったより 有用だ
  46. 概要  ストリームを扱う計算が テンプレートで速くなるという話
  47. ストリーム  (ここでは)何かデータの列  配列とか  外部メモリ上のデータとか  ストリームに対する演算  map  filter  fold  sort  merge  など…
  48. 例  ストリームに対する演算の繰り返し vector<int> v, w, x; for (int i=0; i<100; i++) v.push_back(i); remove_copy_if(v.begin(), v.end(), back_inserter(w), is_even()); transform(w.begin(), w.end(), back_inserter(x), div2());  中間配列ができる  配列を二回なめる  外部メモリを扱う時に特に顕著 for
  49. 1パスでやるには  一か所に書けばいい vector<int> v, x; for (int i=0; i<100; i++) v.push_back(i); for (int i=0; i<v.size(); i++) if (v[i]%2==0) x.push_back(v[i]/2);  モジュラリティが低い  さっきのように書いて、こうなってほしい
  50. 遅延ストリーム  それぞれの処理で一気に配列を舐めるのが いけない → 遅延させてやればいい template <class T> class stream{ … }; template <class S, class F> class filter_stream{ public: typedef S::elem_type elem_type; filter_stream(S &s, F f=F()): s(s), f(f) {} elem_type get(){ for (;;){ elem_type r=s.get(); if (f(r)) return r; } } };
  51. 遅延ストリーム(2) template <class S, class F> class map_stream{ public: typedef S::elem_type elem_type; map_stream(S &s, F f=F()): s(s), f(f) {} elem_type get(){ elem_type r=s.get(); return f(r); } }; stream<int> s; filter_stream<typeof(s), is_even> t(s); map_stream<typeof(t), div2> u(t); while(!u.empty()) cout<<u.get()<<endl;
  52. インライン化  テンプレートで書かれているので インライン化できる  1パスでできる stream<int> s; filter_stream<typeof(s), is_even> t(s); map_stream<typeof(t), div2> u(t); u.get(); // => r=t.get(); return div2(r); // => int r; for (;;){ r=s.get(); if (!is_even(r)) break; } // return div2(r);
  53. yieldとの対比  C#でのyield class FilterStream{ public FilterStream( … ) { … } public IEnumerator<int> GetEnumerator(){ foreach (int t in s) if (!f(t)) yield return i; } …  ループを回すだけのようなことなら 同じようにできる
  54. コルーチン=遅延評価  yield=コルーチンで遅延評価はできる  遅延評価でyieldのようなこともできる  列挙のようなときにはうまくいく
  55. まとめ  C++の謎テクをいくつか紹介  ブロック=継続  Serializable=Reflectable  コルーチン=遅延評価  pficommon近日リリース予定
Advertisement