Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Pfi Seminar 2010 1 7

6,822 views

Published on

  • Be the first to comment

Pfi Seminar 2010 1 7

  1. 1. Caution!  本発表には次のものは含まれません  C++の有用なプログラミング技法  広くつかわれるべきテクニック  その他なにか視聴者に役立つもの  そういうのを期待された方はあしからず
  2. 2. 自己紹介  田中英行 (@tanakh, id:tanakh)  Haskell愛好家  C++歴  1998年 ~  2005年ぐらいまでは漫然と使っていました  社内用C++ライブラリ(pficommon)を作成  C++のきもさを再認識  その辺で得られた知見を話します ○ ※実際に使われているわけではないです
  3. 3. pficommon  Boostの広く利用できる部分(tr1)  データ構造いろいろ  サーバー書く向け機能  マルチスレッド  各種プロトコル  RPC  ウェブアプリ向け機能  DSL
  4. 4. 本日の内容  ユーザー定義構文  Serializable万能説  Stream Fusion  の三本立て
  5. 5. 概要  C++の構文のように扱える構文を 定義してみようとすると、 それがどういう意味を持つのだろうか、 というお話
  6. 6. ユーザー定義構文  ユーザー定義する組み込み構文のように 扱えるもの  例えば  synchronized (Java)  using (C#)  actor (Scala)  のようなものがC++でも書けると嬉しい ですね
  7. 7. ほかの言語の例  Lisp  マクロ・高階関数  Haskell  高階関数・モナドを引数にとる関数  Ruby  ブロックをとる関数  Scala  最後の引数にラムダ式をとる関数を書くと それっぽく見える
  8. 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. 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. 10. 問題点  簡単に書けて、扱いやすいが、 かっこ悪い  foreach(, , {}) ↑ 括弧の中に{}があるのが耐えられない }) って… foreach(int x, v) { cout<<x<<endl; }  のように書きたい
  11. 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. 12. C++でのユーザー定義構文(2)  この書き方は美しいが、 記述力を制限される  既存の制御構文の後ろに文を置けるだけ
  13. 13. 後処理  synchronized(v){ … } のようなことを実 現するには、{ … } の後に処理をさせな ければならない
  14. 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. 15. ifを使う方法  ifの中で変数を宣言すれば、 後続の文の後にデストラクタを動かせる #define synchronized(v) ¥ if (scoped_lock lk=scoped_lock(v))
  16. 16. ifを使う方法(2)  コピー可能なクラスを定義する  trueを返すoperator bool() を定義する  デストラクタを定義する class hoge { … }; if (hoge h=hoge()) stat; 処理の流れ  hoge::hoge()  hoge::operator bool()  stat  hoge::~hoge()
  17. 17. 改良  operator bool() はfalseを返したほうがいい #define synchronized(v) ¥ if (scoped_lock lk=scoped_lock(v)); ¥ else
  18. 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. 19. 議論  hoge(…){ … } の形で、 前処理→処理→後処理 の形のユーザー 定義構文が書けることが分かった  だがそれだけだろうか?  こういうことができるということは、 つまりどういうことなのだろうか
  20. 20. 見えざる継続  継続とは  ある計算過程のある瞬間における、その過 程の未来全体を表すもの、あるいは計算過 程の実行スナップショットと説明される。 (Wikipedia)  Schemeなどでは言語レベルでサポート  継続は、実行中のどんな計算機プログラ ムにも存在する
  21. 21. 関数呼び出しと継続  関数呼び出しとはすなわち、継続をとも なう手続きへのジャンプ  関数が呼ばれた= 関数から返った後の継続が手に入る int foo(){ return 1; } foo()が呼ばれる際に渡される継続 ・返された値に2を書けて void bar() ・それを表示する { cout<<foo()*2<<endl; 実際のところC++では、 } ・スタック ・リターンアドレス の対として表現される
  22. 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. 23. 継続を取り出す  callee save レジスタ、スタック、リター ンアドレスを取り出す cont get_ret_cont(){ regs=get_callee_save_regs(); stack=get_stack(); ret=get_ret_addr(); return (regs, stack, ret); }
  24. 24. 継続が取り出せるなら  operator bool()にて、継続を取り出し、 さらに文実行後の継続を設定し、stat後 の処理の流れを決められる hoge::operator bool() { cont c=get_ret_cont(); next=…; c(true); } hoge::~hoge() { next(); }
  25. 25. 文を後ろに置く=継続を渡す  つまるところ、if (hoge h=hoge()) { … } は 後ろの文を引数に関数を呼び出しているの と同じである  Schemeの高階関数や、Rubyのブロック構 文と同じことができる
  26. 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. 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. 28. 別スレッドでの継続の起動  スタックを別のスタックにコピーする  ポインタの張り替えなどめんどい ret ret ret frame frame frame ret ret ret frame frame frame
  29. 29. まとめ  C++でユーザー定義構文は意外ときれい に作れるんじゃなかろうか  ポータブルな実装ができるかどうか
  30. 30. 概要  シリアライザというものがあります  シリアライザが意外と色々なところに使 えるという話
  31. 31. シリアライザとは  データをバイト列に変換したり(シリアラ イズ)、バイト列からデータに変換したり (デシリアライズ)するもの  クラスやコンテナの内容をファイルに保存 したり、ネットワーク越しに転送したりす るのにとても便利
  32. 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. 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. 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. 35. シリアライズするというのはどうい うことか?  クラスのシリアライズ  メンバの列挙  コンテナのシリアライズ  データの列挙
  36. 36. テンプレートでのシリアライズ  serializeがテンプレートメンバ関数  シリアライズ・デシリアライズを 共通化  色々なシリアライザ(text, binary)に適用 可能  拡張可能  → シリアライザじゃなくてもいいので は?
  37. 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. 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. 39. 型を書きだす(3)  型情報取得関数 template <class T> type_info *get_type() { T v; type_oarchive oa; oa << v; return oa.get(); }
  40. 40. Serializable = Reflectable  値の代わりに型を書きだすことにより、 Serializableなクラスは(部分的には) Reflectableなクラスとなる  Reflectableなクラスは明らかに Serializableに出来るのでこれはそう おかしな話ではない  つまり、(部分的には)Serializableと Reflectableは等価である
  41. 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. 42. RPC説明  適当に関数のシグニチャを定義して、 それに合う関数をセットする  関数の引数、返り値はソケットでやり取 りされる → シリアライズ可能でなけれ ばならない
  43. 43. RPC:クライアントコード生成  言語bindingを自動で生成  C++ソースを読み込む  パーズするのは大変すぎる  g++にやらせる  リフレクションする  RPCの型情報が取れる  好きなコード生成できる  RPCでやり取りするデータはすべてSerializable でなければならないはずなので、すべて何もし なくてもリフレクション出来るはずである for
  44. 44. RPC:テスト生成  おなじ原理でRPCテスト用のWebサー バーをC++コードから自動で生成
  45. 45. まとめ  Serializableなデータ構造は思ったより 有用だ
  46. 46. 概要  ストリームを扱う計算が テンプレートで速くなるという話
  47. 47. ストリーム  (ここでは)何かデータの列  配列とか  外部メモリ上のデータとか  ストリームに対する演算  map  filter  fold  sort  merge  など…
  48. 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. 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. 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. 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. 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. 53. yieldとの対比  C#でのyield class FilterStream{ public FilterStream( … ) { … } public IEnumerator<int> GetEnumerator(){ foreach (int t in s) if (!f(t)) yield return i; } …  ループを回すだけのようなことなら 同じようにできる
  54. 54. コルーチン=遅延評価  yield=コルーチンで遅延評価はできる  遅延評価でyieldのようなこともできる  列挙のようなときにはうまくいく
  55. 55. まとめ  C++の謎テクをいくつか紹介  ブロック=継続  Serializable=Reflectable  コルーチン=遅延評価  pficommon近日リリース予定

×