squirrel_code @ ウィキ
続続 template × mix-in
最終更新:
squirrel_code
-
view
続続 template × mix-in
last update: 2010/12/28 (Tue)
というわけで,なんとかしてみる.
その4
まず, Interface について考えてみる.重要な制約は, C++ では関数テンプレートが仮想にできないという点だ.よって最後に MixExport した関数テンプレートは,どう頑張っても Interface からは呼べないことになる.やはりこの方針はまずい.
で,いろいろ調べていたら using 宣言というのが目に止まった. using namespace std の using ディレクティブとは違い,メソッドやメンバを「引数」にとる.通常,基底クラスで宣言されたメソッドなりメンバなりは,派生クラスで同名のメンバやメソッドが宣言されると隠蔽される.しかし using 宣言することにより,隠蔽をはずすことができる.これだ!
書いてみる.
// mixin.h #include <list> // for std::list #include <algorithm> // for std::find // コンテナに格納されるクラス class A { /* 省略 */ } class B { /* 省略 */ } // コンテナ機能を付加する Mix-in のインターフェイステンプレート template <typename T> class MI { public: virtual ~MI() {} void add(T* t) = 0; void remove(T* t) = 0; }; // コンテナ機能を付加する Mix-in のテンプレート template <typename T> class MixIn { std::list<T> ts; public: typedef T type; MixIn() {} ~MixIn() {} void add(T* t); void remove(T* t); }; // MixIn の実装 template <typename T> void MixIn<T>::add(T* t) { ts.push_back(t); } template <typename T> void MixIn::remove(T* t); { typedef typename std::list<T>::iterator iter; iter i = std::find(ts.begin(), ts.end(), t); if (i != ts.end()) ts.erase(i++); } // デフォルトの基底クラス class DummyBase { public: void add(); void remove(); }; // インターフェイスのメソッド名を輸出するテンプレート template <typename M, typename Base = DummyBase> class IMix : public Base, public M { public: using Base::add; using Base::remove; using M::add; using M::remove; }; // 実装を Mix-in しながら名前を輸出するテンプレート template <typename M, typename Base = DummyBase> class Mix : public Base, private M { public: using Base::add; using Base::remove; void add(typename M::type* t) { M::add(t); } void remove(typename M::type* t) { M::remove(t); } }; // Mix-in されるインタフェイス class Interface : public IMix<MI<A>, IMix<MI<B> > > { public: virtual Interface() {} }; // Mix-in されるクラス class Container : public Mix<MixIn<A>, Mix<MixIn<B>, Interface> > { public: Container() {} ~Container() {} }; // mixin.cpp #include "mixin.h"
ブラボー.インターフェイスも実装も再利用でき,無駄な名前の輸出もなく,加えて Interface のポインタにも MI<A> や MI<B> のポインタにも代入できる.もちろんどのポインタ経由でもデストラクタがちゃんと呼ばれる.
さて,これで万事解決だろうか.IMix<MI<A>, IMix<MI<B> > > とか Mix<MixIn<A>, Mix<MixIn<B>, Interface> > とか,ちょっとややこしすぎないだろうか.
こういう再帰っぽいパターンってなんとなく関数プログラミングっぽい.そういえば boost のテンプレートメタプログラミングって関数プログラミングっぽいと聞いた覚えがある.ん,何かいいのある?・・・と調べていたら, そっくりなの があった.これを改造して, Mix-in に使えないだろうか.
やってみる.
その5
// mixin.h #include <list> // for std::list #include <algorithm> // for std::find #include <boost/mpl/list.hpp> #include <boost/mpl/iter_fold.hpp> #include <boost/mpl/deref.hpp> #include <boost/type.hpp> namespace mpl = boost::mpl; using mpl::_1; using mpl::_2; // コンテナに格納されるクラス class A { /* 省略 */ } class B { /* 省略 */ } // コンテナ機能を付加する Mix-in のインターフェイステンプレート template <typename T> class MI { public: virtual ~MI() {} void add(T* t) = 0; void remove(T* t) = 0; }; // コンテナ機能を付加する Mix-in のテンプレート template <typename T> class MixIn { std::list<T> ts; public: typedef T t_type; MixIn() {} ~MixIn() {} void add(T* t); void remove(T* t); }; // MixIn の実装 template <typename T> void MixIn<T>::add(T* t) { ts.push_back(t); } template <typename T> void MixIn::remove(T* t); { typedef typename std::list<T>::iterator iter; iter i = std::find(ts.begin(), ts.end(), t); if (i != ts.end()) ts.erase(i++); } // デフォルトの基底クラス class DummyBase { public: void add(); void remove(); }; // 型イテレータと基底を受け取りインターフェイスを返す template <typename MIter, typename Base> class IMix1 : public Base, public mpl::deref<MIter>::type { public: typedef typename mpl::deref<MIter>::type M; using Base::add; using Base::remove; using M::add; using M::remove; }; // 型リストに含まれるインターフェイスの Mix-in template <typename TList> class IMix : public mpl::iter_fold<TList, DummyBase, IMix1<_2, _1> >::type {}; // 型イテレータと基底を受け取り Mix-in されたクラスを返す template <typename MIter, typename Base> class Mix1 : public Base, private mpl::deref<MIter>::type { public: typedef typename mpl::deref<MIter>::type M; using Base::add; using Base::remove; void add(typename M::t_type* t) { M::add(t); } void remove(typename M::t_type* t) { M::remove(t); } }; // 型リストに含まれる MixIn の Mix-in template <typename TList, typename Base = DummyBase> class Mix : public mpl::iter_fold<TList, Base, Mix1<_2, _1> >::type {}; // インターフェイス class Interface : public IMix<mpl::list<MI<A>, MI<B> > > { public: virtual ~Interface() {} }; // Mix-in されるクラス class Container : public Mix<mpl::list<MixIn<A>, MixIn<B> >, Interface> { public: Container() {} ~Container() {} }; // mixin.cpp #include "mixin.h"
こんなのがよくコンパイラ通るもんだとちょっと感心^^.MixIn のインターフェイス,Interface クラス,MixIn クラス,Container クラスいずれも非常にシンプル. MPL すげー.
ここで疑問.実装側も IMix 使えばいいんじゃね?
これはたぶんできる(確認するのめんどくさい^^).こうすると Mix と IMix を 2 つ書く必要がないし, 続続続 template × mix-in で書くような汎用 Mix-in フレームワークで名前の輸出をわざわざコーディングする必要もなくなる.
ただし, Container クラスが MixIn<A> や MixIn<B> の公開派生クラスとなるので, Container クラスのポインタを MixIn<A> や MixIn<B> のポインタに代入できてしまう.この状態で delete されうることを考えると, MixIn のデストラクタを virtual にしないといけない.
好みの問題だと思うが,実装は public 継承しないほうが美しいと思う.小規模なプログラムを書く程度なら, IMix の使いまわしでいいかもね.
&trackback()
参考
コメント
- コメントの投稿テスト -- (tossy_squirrel) 2010-12-29 03:35:18