構造とI/Fのマップ
このへんに時空を越えて(いまさら)反応してみる。
- アルゴリズム(が必要とするI/F)
- データ構造
- データ構造をI/Fにどうマップするか
はちゃんと別の概念になってる。つまりinterface(や純粋仮想クラス)という奴がそれ。
例えば、Runnableインタフェースを要求するアルゴリズムはRunnableのメソッドをI/Fとして要求する。あるデータ構造(自作のスレッドクラスなど)をどうマップするかは、Runメソッドの実装として表現される。
あるinterfaceをimplementsするということは、そのinterfaceが要求するI/Fへのマップ機能を提供しますよ、と宣言していることと同義だ。
問題は、「I/Fへのマップ」の粒度が粗すぎたことと、クラス定義の時点で実装しなければならかった、という2点にある。
STLのいくつかのアルゴリズムでは、「I/Fへのマップ」はファンクタという形で実現される。例えばstd::sortなんかがそうだ。
struct Foo { bool operator < (const Foo& x) const { ... } ... }; vector<Foo> foo_v; sort(foo_v.begin(), foo_v.end()); struct Bar { // operator <を持たない ... }; // sortアルゴリズムが要求するI/FをBarのデータ構造にマップする struct less_for_Bar { bool operator() (const Bar& x, const Bar& y) const { ... } }; vector<Bar> bar_v; sort(bar_v.begin(), bar_v.end(), less_for_Bar);
こうすることで、粗かった粒度は(限界まで)細かくなり、実装のタイミングもクラス定義から分離できるわけだ。
ただ、これでそれなりに十分なのは、STLアルゴリズムが要求するI/Fの数が少ないからだ。複雑なアルゴリズムであればより多くのI/Fを要求する。そこで、conceptの出番とあいなる。
conceptは「要求するI/Fの集合」で、concept_mapは上でいうファンクタの集合みたいなもの。
conceptにはメンバ関数一個単位で足し引きできるから、粒度の問題を解決できる。またconcept_mapはどの後からいくらでも定義できるから、実装のタイミングをクラス定義から分離できる。
「JavaにinterfaceやGenericのwhereで抜かれた!」とお嘆きの諸君(それは私)、われらがC++はやってくれた!一発大逆転だ、ひゃっほう。
まあでも
現行のC++の場合は、アルゴリズムがPolicyテンプレート経由でデータを操作するようにして、データ構造ごとにそれ専用の「構造とI/Fのマップ」をtemplate specializationで定義してやればOKなので。conceptに比べて薄皮が1枚挟まった感じがするけど、できないわけじゃない。
C言語でも、あるデータ構造専用の「構造とI/Fのマップ」を、例えば関数ポインタの集合として持たせれば似たようなことができるよね。例えばsusieプラグインのI/Fとか(って古いな)。
注意
ちなみに私、Javaでまともにプログラム書いたことないので、細かい間違いは大目に見てください。
…あと、conceptもまだ仕様書ちゃんと読んでなかったりします(ぉぃ