最終更新: 2010-01-15T21:20+0900
読みやすくも正確にも書けないのでリンクだけ。
サクラエディタの肥大化する一方の CEditView(描画、ダイアログの表示、文字列検索、単語判定、URL判定、ドラッグアンドドロップ対応、などなどなんでもござれ)を機能ごとに分割したいなあ、と考えていて、モデルとして想定していたのが Rubyの Enumerableモジュールのミックスイン。Enumerableを ミックスイン(include)するクラスが eachメソッドを提供するだけで、およそシーケンシャルアクセス可能なコレクションに対して行いたい操作(max, min, find, map,...)のすべてが可能になるという仕組み。
C++でやるにはどうするのかな、と想像していたときに「これってあれじゃね?」と結びついたのが「奇妙に再帰したテンプレートパターン(CRTP)」。このパターンは「[単行本(ソフトカバー)] ビョルン・カールソン【Boost C++をチューンアップする最先端ライブラリ】 ピアソンエデュケーション」で仕入れた。
仮に Enumerableモジュールのようなものをこのパターンで実現できたとして、その Enumerableテンプレート(eachメソッドが定義されたクラスをテンプレートパラメータとして受け取る)が配列っぽいあれやこれやのクラスにミックスイン(継承)されたらオブジェクトファイルがすごく肥大化しそうだという気がする。
純粋仮想関数にした eachに依存する形で Enumerableの各メソッドを実装すれば、テンプレートを使ったときと違って実装の共有ができるだろうか。
というようなことを想像してるだけ。
eachが yieldする型は何になるんだ?(C++で yieldなんてできないという話はおいておいて) 結局、型安全性を求めたらテンプレートになって、型ごとに実装(テンプレートのインスタンス)が作られるんじゃないか。
と、ここで boost::anyを使ってはどうか。Enumerableのメソッドの実装は要素の型に依存しないから anyのまま扱う。Enumerableのメソッドを呼び出す側が要素の型を知っているから anyから正しい型のオブジェクトを取り出せる。
と思ったら、「Enumerableのメソッドの実装は要素の型に依存しない」は嘘だ。比較できないと最大値や最小値が求まらん。Enumerableがコレクションに対して求めてるのは eachだけだけど、その要素に対しては比較できることや加算できることなど、いろいろ要求している。これじゃあテンプレートでないとやってられない。
いやいやいや、min, maxをはじめ Enumerableのほとんどのメソッドは要素を引数にとるブロックを受け付けて、その結果を基に処理を行っている。ブロックのコンテキストは Enumerableのメソッドを呼び出す側だから要素の型を知っている。やっぱり Enumerableの実装は要素の型を知らなくてもいい。
ブロックの代わりになるのは関数オブジェクトか traitsかな。
とまあ、かように面倒くさそうなことになるから CEditViewクラスになんでも突っ込む結果になるのも当然。言語の不備だ、とか言ってみる。
あるクラス(CEditDoc, CEditView)にいろいろな特性(検索可能性、ドラッグアンドドロップ可能性、範囲選択可能性、……)を、簡単にクラス外から付加する確立した手順が必要。……と、あくまで想像するだけ。
それに分割してしまうと機能間での連携(依存ともいう)に苦労することになるのが目に見えている。ANSI版の CEditViewはなんでも一カ所につっこめる限界点を超えているとは思うが。
mix-inや traitsは、使われる場所でいろいろ意味が違うみたい。自分は mix-inといえば Rubyのしか知らないし、traitsといえば std::stringの char_traitsしか知らない(それすら知っているとはいえない)。