More Related Content
Similar to Pfi Seminar 2010 1 7
Similar to Pfi Seminar 2010 1 7 (20)
More from Preferred Networks
More from Preferred Networks (20)
Pfi Seminar 2010 1 7
- 3. Caution!
本発表には次のものは含まれません
C++の有用なプログラミング技法
広くつかわれるべきテクニック
その他なにか視聴者に役立つもの
そういうのを期待された方はあしからず
- 4. 自己紹介
田中英行 (@tanakh, id:tanakh)
Haskell愛好家
C++歴
1998年 ~
2005年ぐらいまでは漫然と使っていました
社内用C++ライブラリ(pficommon)を作成
C++のきもさを再認識
その辺で得られた知見を話します
○ ※実際に使われているわけではないです
- 8. 概要
C++の構文のように扱える構文を
定義してみようとすると、
それがどういう意味を持つのだろうか、
というお話
- 10. ほかの言語の例
Lisp
マクロ・高階関数
Haskell
高階関数・モナドを引数にとる関数
Ruby
ブロックをとる関数
Scala
最後の引数にラムダ式をとる関数を書くと
それっぽく見える
- 11. 例: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)
} }
- 12. 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; })
- 13. 問題点
簡単に書けて、扱いやすいが、
かっこ悪い
foreach(, , {})
↑ 括弧の中に{}があるのが耐えられない
}) って…
foreach(int x, v) {
cout<<x<<endl;
}
のように書きたい
- 14. 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; }
- 16. 後処理
synchronized(v){ … } のようなことを実
現するには、{ … } の後に処理をさせな
ければならない
- 17. 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が食われてしまう
- 18. ifを使う方法
ifの中で変数を宣言すれば、
後続の文の後にデストラクタを動かせる
#define synchronized(v) ¥
if (scoped_lock lk=scoped_lock(v))
- 20. 改良
operator bool() はfalseを返したほうがいい
#define synchronized(v) ¥
if (scoped_lock lk=scoped_lock(v)); ¥
else
- 21. 活用例: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__;
}
}
}
};
- 22. 議論
hoge(…){ … } の形で、
前処理→処理→後処理 の形のユーザー
定義構文が書けることが分かった
だがそれだけだろうか?
こういうことができるということは、
つまりどういうことなのだろうか
- 23. 見えざる継続
継続とは
ある計算過程のある瞬間における、その過
程の未来全体を表すもの、あるいは計算過
程の実行スナップショットと説明される。
(Wikipedia)
Schemeなどでは言語レベルでサポート
継続は、実行中のどんな計算機プログラ
ムにも存在する
- 24. 関数呼び出しと継続
関数呼び出しとはすなわち、継続をとも
なう手続きへのジャンプ
関数が呼ばれた=
関数から返った後の継続が手に入る
int foo(){ return 1; } foo()が呼ばれる際に渡される継続
・返された値に2を書けて
void bar() ・それを表示する
{
cout<<foo()*2<<endl; 実際のところC++では、
} ・スタック
・リターンアドレス
の対として表現される
- 25. ユーザー定義構文では
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() なる継続
- 26. 継続を取り出す
callee save レジスタ、スタック、リター
ンアドレスを取り出す
cont get_ret_cont(){
regs=get_callee_save_regs();
stack=get_stack();
ret=get_ret_addr();
return (regs, stack, ret);
}
- 27. 継続が取り出せるなら
operator bool()にて、継続を取り出し、
さらに文実行後の継続を設定し、stat後
の処理の流れを決められる
hoge::operator bool()
{
cont c=get_ret_cont();
next=…;
c(true);
}
hoge::~hoge()
{
next();
}
- 29. 例:スレッド
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--;
}
- 30. 実装
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);
}
- 36. 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;
}
};
- 37. boost::serializationの特徴
シリアライズとデシリアライズを
共通のコードで記述
テンプレートでディスパッチ
hoge h;
text_oarchive oa(cout);
oa << h; // text_oarchiveを引数にserializeが呼ばれる
hoge g;
text_iarchive ia(cin);
ia >> h; // text_iarchiveを引数にserializeが呼ばれる
- 38. 仕組み
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);
}
- 41. 型を書きだす
データの代わりに、型を書きだしてみる
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)));
}
…
- 42. 型を書きだす(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>());
}
- 43. 型を書きだす(3)
型情報取得関数
template <class T>
type_info *get_type()
{
T v;
type_oarchive oa;
oa << v;
return oa.get();
}
- 44. Serializable = Reflectable
値の代わりに型を書きだすことにより、
Serializableなクラスは(部分的には)
Reflectableなクラスとなる
Reflectableなクラスは明らかに
Serializableに出来るのでこれはそう
おかしな話ではない
つまり、(部分的には)Serializableと
Reflectableは等価である
- 45. 応用例: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);
}
- 47. RPC:クライアントコード生成
言語bindingを自動で生成
C++ソースを読み込む
パーズするのは大変すぎる
g++にやらせる
リフレクションする
RPCの型情報が取れる
好きなコード生成できる
RPCでやり取りするデータはすべてSerializable
でなければならないはずなので、すべて何もし
なくてもリフレクション出来るはずである
for
- 49. まとめ
Serializableなデータ構造は思ったより
有用だ
- 51. 概要
ストリームを扱う計算が
テンプレートで速くなるという話
- 52. ストリーム
(ここでは)何かデータの列
配列とか
外部メモリ上のデータとか
ストリームに対する演算
map
filter
fold
sort
merge
など…
- 53. 例
ストリームに対する演算の繰り返し
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
- 54. 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);
モジュラリティが低い
さっきのように書いて、こうなってほしい
- 55. 遅延ストリーム
それぞれの処理で一気に配列を舐めるのが
いけない → 遅延させてやればいい
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;
}
}
};
- 56. 遅延ストリーム(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;
- 57. インライン化
テンプレートで書かれているので
インライン化できる
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);
- 58. yieldとの対比
C#でのyield
class FilterStream{
public FilterStream( … ) { … }
public IEnumerator<int> GetEnumerator(){
foreach (int t in s)
if (!f(t))
yield return i;
}
…
ループを回すだけのようなことなら
同じようにできる
- 60. まとめ
C++の謎テクをいくつか紹介
ブロック=継続
Serializable=Reflectable
コルーチン=遅延評価
pficommon近日リリース予定