pfi seminar 2010 1 7

60

Upload: preferred-infrastructure-preferred-networks

Post on 30-Jun-2015

5.795 views

Category:

Documents


3 download

TRANSCRIPT

Page 1: Pfi Seminar 2010 1 7
Page 2: Pfi Seminar 2010 1 7
Page 3: Pfi Seminar 2010 1 7

Caution!

本発表には次のものは含まれません

C++の有用なプログラミング技法

広くつかわれるべきテクニック

その他なにか視聴者に役立つもの

そういうのを期待された方はあしからず

Page 4: Pfi Seminar 2010 1 7

自己紹介

田中英行 (@tanakh, id:tanakh)

Haskell愛好家

C++歴 1998年 ~

2005年ぐらいまでは漫然と使っていました

社内用C++ライブラリ(pficommon)を作成 C++のきもさを再認識

その辺で得られた知見を話します○ ※実際に使われているわけではないです

Page 5: Pfi Seminar 2010 1 7

pficommon

Boostの広く利用できる部分(tr1)

データ構造いろいろ

サーバー書く向け機能

マルチスレッド

各種プロトコル

RPC

ウェブアプリ向け機能

DSL

Page 6: Pfi Seminar 2010 1 7

本日の内容

ユーザー定義構文

Serializable万能説

Stream Fusion

の三本立て

Page 7: Pfi Seminar 2010 1 7
Page 8: Pfi Seminar 2010 1 7

概要

C++の構文のように扱える構文を定義してみようとすると、それがどういう意味を持つのだろうか、というお話

Page 9: Pfi Seminar 2010 1 7

ユーザー定義構文

ユーザー定義する組み込み構文のように扱えるもの

例えば

synchronized (Java)

using (C#)

actor (Scala)

のようなものがC++でも書けると嬉しいですね

Page 10: Pfi Seminar 2010 1 7

ほかの言語の例

Lisp

マクロ・高階関数

Haskell

高階関数・モナドを引数にとる関数

Ruby

ブロックをとる関数

Scala

最後の引数にラムダ式をとる関数を書くとそれっぽく見える

Page 11: Pfi Seminar 2010 1 7

例:foreach

(for-each (lambda (x)(display x)

‘(1 2 3 4 5))

(1..5).each{|x|p x

}

forM_ [1..5] $ ¥x ->print x

List(1,2,3,4,5).foreach{ x =>println(x)

}

Page 12: Pfi Seminar 2010 1 7

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; })

Page 13: Pfi Seminar 2010 1 7

問題点

簡単に書けて、扱いやすいが、

かっこ悪い foreach(, , {})

↑ 括弧の中に{}があるのが耐えられない}) って…

のように書きたい

foreach(int x, v) {cout<<x<<endl;

}

Page 14: Pfi Seminar 2010 1 7

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; }

Page 15: Pfi Seminar 2010 1 7

C++でのユーザー定義構文(2)

この書き方は美しいが、記述力を制限される

既存の制御構文の後ろに文を置けるだけ

Page 16: Pfi Seminar 2010 1 7

後処理

synchronized(v){ … } のようなことを実現するには、{ … } の後に処理をさせなければならない

Page 17: Pfi Seminar 2010 1 7

forを使う方法

後続の文より後に処理をさせる方法

forを使う

break, continueが食われてしまう

#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)

Page 18: Pfi Seminar 2010 1 7

ifを使う方法

ifの中で変数を宣言すれば、後続の文の後にデストラクタを動かせる

#define synchronized(v) ¥if (scoped_lock lk=scoped_lock(v))

Page 19: Pfi Seminar 2010 1 7

ifを使う方法(2)

コピー可能なクラスを定義する

trueを返すoperator bool() を定義する

デストラクタを定義する

class hoge { … };

if (hoge h=hoge()) stat;

処理の流れ hoge::hoge() hoge::operator bool() stat hoge::~hoge()

Page 20: Pfi Seminar 2010 1 7

改良

operator bool() はfalseを返したほうがいい

#define synchronized(v) ¥if (scoped_lock lk=scoped_lock(v)); ¥else

Page 21: Pfi Seminar 2010 1 7

活用例: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__;

}

}

}

};

Page 22: Pfi Seminar 2010 1 7

議論

hoge(…){ … } の形で、前処理→処理→後処理 の形のユーザー定義構文が書けることが分かった

だがそれだけだろうか?

こういうことができるということは、つまりどういうことなのだろうか

Page 23: Pfi Seminar 2010 1 7

見えざる継続

継続とは

ある計算過程のある瞬間における、その過程の未来全体を表すもの、あるいは計算過程の実行スナップショットと説明される。(Wikipedia)

Schemeなどでは言語レベルでサポート

継続は、実行中のどんな計算機プログラムにも存在する

Page 24: Pfi Seminar 2010 1 7

関数呼び出しと継続

関数呼び出しとはすなわち、継続をともなう手続きへのジャンプ

関数が呼ばれた=関数から返った後の継続が手に入る

int foo(){ return 1; }

void bar(){cout<<foo()*2<<endl;

}

foo()が呼ばれる際に渡される継続・返された値に2を書けて・それを表示する

実際のところC++では、・スタック・リターンアドレスの対として表現される

Page 25: Pfi Seminar 2010 1 7

ユーザー定義構文では

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() なる継続

Page 26: Pfi Seminar 2010 1 7

継続を取り出す

callee save レジスタ、スタック、リターンアドレスを取り出す

cont get_ret_cont(){regs=get_callee_save_regs();stack=get_stack();ret=get_ret_addr();return (regs, stack, ret);

}

Page 27: Pfi Seminar 2010 1 7

継続が取り出せるなら

operator bool()にて、継続を取り出し、さらに文実行後の継続を設定し、stat後の処理の流れを決められる

hoge::operator bool(){cont c=get_ret_cont();next=…;c(true);

}hoge::~hoge(){next();

}

Page 28: Pfi Seminar 2010 1 7

文を後ろに置く=継続を渡す

つまるところ、if (hoge h=hoge()) { … } は

後ろの文を引数に関数を呼び出しているのと同じである

Schemeの高階関数や、Rubyのブロック構文と同じことができる

Page 29: Pfi Seminar 2010 1 7

例:スレッド

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--;}

Page 30: Pfi Seminar 2010 1 7

実装

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);

}

Page 31: Pfi Seminar 2010 1 7

別スレッドでの継続の起動

スタックを別のスタックにコピーする

ポインタの張り替えなどめんどい

retframe

retframe

retframe

retframe

retframe

retframe

Page 32: Pfi Seminar 2010 1 7

まとめ

C++でユーザー定義構文は意外ときれいに作れるんじゃなかろうか

ポータブルな実装ができるかどうか

Page 33: Pfi Seminar 2010 1 7
Page 34: Pfi Seminar 2010 1 7

概要

シリアライザというものがあります

シリアライザが意外と色々なところに使えるという話

Page 35: Pfi Seminar 2010 1 7

シリアライザとは

データをバイト列に変換したり(シリアライズ)、バイト列からデータに変換したり(デシリアライズ)するもの

クラスやコンテナの内容をファイルに保存したり、ネットワーク越しに転送したりするのにとても便利

Page 36: Pfi Seminar 2010 1 7

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;

}};

Page 37: Pfi Seminar 2010 1 7

boost::serializationの特徴

シリアライズとデシリアライズを共通のコードで記述

テンプレートでディスパッチ

hoge h;text_oarchive oa(cout);oa << h; // text_oarchiveを引数にserializeが呼ばれる

hoge g;text_iarchive ia(cin);ia >> h; // text_iarchiveを引数にserializeが呼ばれる

Page 38: Pfi Seminar 2010 1 7

仕組み

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);

}

Page 39: Pfi Seminar 2010 1 7

シリアライズするというのはどういうことか?

クラスのシリアライズ

メンバの列挙

コンテナのシリアライズ

データの列挙

Page 40: Pfi Seminar 2010 1 7

テンプレートでのシリアライズ

serializeがテンプレートメンバ関数

シリアライズ・デシリアライズを共通化

色々なシリアライザ(text, binary)に適用可能

拡張可能

→ シリアライザじゃなくてもいいのでは?

Page 41: Pfi Seminar 2010 1 7

型を書きだす

データの代わりに、型を書きだしてみる

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)));

}…

Page 42: Pfi Seminar 2010 1 7

型を書きだす(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>());

}

Page 43: Pfi Seminar 2010 1 7

型を書きだす(3)

型情報取得関数

template <class T>type_info *get_type(){T v;type_oarchive oa;oa << v;return oa.get();

}

Page 44: Pfi Seminar 2010 1 7

Serializable = Reflectable

値の代わりに型を書きだすことにより、Serializableなクラスは(部分的には)

Reflectableなクラスとなる

Reflectableなクラスは明らかにSerializableに出来るのでこれはそうおかしな話ではない

つまり、(部分的には)SerializableとReflectableは等価である

Page 45: Pfi Seminar 2010 1 7

応用例:RPC

// シグニチャRPC_PROC(add, int(int, int)) // メソッド定義

RPC_GEN(calc, add) // クラス定義

// サーバint add(int x, int y){ return x+y; }

int main(){

hoge_server serv;

serv.set_add(&add);

serv.serv(12345, 10);

}

// クライアントhoge_client cli(“localhost”, 12345);

cout<<cli.call_add(1,2)<<endl;

// ↑ 3が返ってくるはず

Page 46: Pfi Seminar 2010 1 7

RPC説明

適当に関数のシグニチャを定義して、それに合う関数をセットする

関数の引数、返り値はソケットでやり取りされる → シリアライズ可能でなければならない

Page 47: Pfi Seminar 2010 1 7

RPC:クライアントコード生成

言語bindingを自動で生成

C++ソースを読み込む

パーズするのは大変すぎる

g++にやらせる

リフレクションする

RPCの型情報が取れる

好きなコード生成できる

RPCでやり取りするデータはすべてSerializable

でなければならないはずなので、すべて何もしなくてもリフレクション出来るはずである

for

Page 48: Pfi Seminar 2010 1 7

RPC:テスト生成

おなじ原理でRPCテスト用のWebサーバーをC++コードから自動で生成

Page 49: Pfi Seminar 2010 1 7

まとめ

Serializableなデータ構造は思ったより有用だ

Page 50: Pfi Seminar 2010 1 7
Page 51: Pfi Seminar 2010 1 7

概要

ストリームを扱う計算がテンプレートで速くなるという話

Page 52: Pfi Seminar 2010 1 7

ストリーム

(ここでは)何かデータの列 配列とか

外部メモリ上のデータとか

ストリームに対する演算 map

filter

fold

sort

merge

など…

Page 53: Pfi Seminar 2010 1 7

ストリームに対する演算の繰り返し

中間配列ができる

配列を二回なめる

外部メモリを扱う時に特に顕著for

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());

Page 54: Pfi Seminar 2010 1 7

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);

Page 55: Pfi Seminar 2010 1 7

遅延ストリーム

それぞれの処理で一気に配列を舐めるのがいけない → 遅延させてやればいい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;

}}

};

Page 56: Pfi Seminar 2010 1 7

遅延ストリーム(2)

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;

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);

}};

Page 57: Pfi Seminar 2010 1 7

インライン化

テンプレートで書かれているのでインライン化できる

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);

Page 58: Pfi Seminar 2010 1 7

yieldとの対比

C#でのyield

ループを回すだけのようなことなら同じようにできる

class FilterStream{public FilterStream( … ) { … }public IEnumerator<int> GetEnumerator(){foreach (int t in s)

if (!f(t))yield return i;

}…

Page 59: Pfi Seminar 2010 1 7

コルーチン=遅延評価

yield=コルーチンで遅延評価はできる

遅延評価でyieldのようなこともできる

列挙のようなときにはうまくいく

Page 60: Pfi Seminar 2010 1 7

まとめ

C++の謎テクをいくつか紹介

ブロック=継続

Serializable=Reflectable

コルーチン=遅延評価

pficommon近日リリース予定