squirrel_code @ ウィキ

続 template × mix-in

最終更新:

squirrel_code

- view
管理者のみ編集可

続 template × mix-in

last update: 2010/12/28 (Tue)


というわけで,なんとかしてみる.

その2


その前に,少し落ち着いて考えてみる.そもそもメソッドのオーバーロードが必要となったのは,MixInA と MixInB が「ほぼ同じ」機能を持っていたからだ.MixInA と MixInB が全く異なる機能を持っているなら,そもそも同名のメソッドがあること自体,筋が悪い.同名のメソッドが無いならば,複数のクラスを Mix-in してもあまり問題はなさそうだ(実はあるんだが^^).

なので,対処すべきは,複数 Mix-in されるクラスが,
  • 全て同名のメソッド群を持ち
  • ただし引数 or 戻り値が全て異なる
場合だろう.

となれば, Mix-in されるクラスは template 化されているべきだろう.やってみる.MixInA と MixInB の実装をまとめられる以外に,何かご利益があるだろうか.

// mixin.h
#include <list>      // for std::list
#include <algorithm> // for std::find

// コンテナに格納されるクラス
class A { /* 省略 */ }
class B { /* 省略 */ }

// コンテナ機能を付加する Mix-in のテンプレート
template <typename T>
class MixIn
{
    std::list<T> ts;
public:
    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++);
}

// Mix-in されるクラス
class Container : private MixIn<A>, private MixIn<B>
{
public:
    Container() : MixIn<A>(), MixIn<B>() {}
    ~Container() {}
    
    template <typename T>
    void add(T* t)
        { MixIn<T>::add(t); }
    
    template <typename T>
    void remove(T* t)
        { MixIn<T>::remove(t); }
};

// mixin.cpp
#include "mixin.h"

テンプレート化されたので,list::iterator の typedef 時に typename が必要になることに注意.

テンプレート化にともない, Container クラス内での名前の輸出をテンプレート関数にすることが可能となる.これで複数の Mix-in で提供されるメソッドの名前を全て輸出しなくてもよくなる.

とはいえ,やっぱり名前の輸出が必要なことに代わりはない.なんとかならないだろうか.

その3


ここで呼吸をととのえて,さらに考えてみる.Container クラス内での名前の輸出は,当然ながら Container クラスの public スコープへの干渉操作となる.これを public 継承で実現してはならないのは「その1」で見た通り.これをなんとかするには,続続 pImpl × 継承 でやったように,テンプレートを用いる.

ところが,続続 pImpl × 継承 で使った名前の輸出テンプレートは,各 Mix-in を private/protected 継承している必要がある.よって,MixIn<A> と MixIn<B> を同時に Mix-in するためには,この2つを Mix-in するための専用のテンプレートが必要だ.Mix-in の種類が増えてくると,様々な Mix-in の組み合わせ全てに専用のテンプレートを用意しなくてはならない.

そこでうーんと考えた.Mix-in の組み合わせを private 継承する操作を,Mix-in 1つの protected 継承と public 継承に置き換えられないだろうか.書いてみる.

// mixin.h
#include <list>      // for std::list
#include <algorithm> // for std::find

// コンテナに格納されるクラス
class A { /* 省略 */ }
class B { /* 省略 */ }

// コンテナ機能を付加する Mix-in のテンプレート
template <typename T>
class MixIn
{
    std::list<T> ts;
public:
    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 {};

// 1つづつ Mix-in するテンプレート
template <typename M, typename Base = DummyBase>
class Mix : public Base, protected M
{};

// Mix-in しつつ名前の輸出もするテンプレート
template <typename M, typename Base = DummyBase>
clas MixExport : public Base, protected M
{
public:
    template <typename T>
    void add(T* t)
        { MixIn<T>::add(t); }
    void remove(T* t)
        { MixIn<T>::remove(t); }
};

// Mix-in されるクラス
class Container
    : public MixExport<MixIn<A>, Mix<MixIn<B> > >
{
public:
    Container() {}
    ~Container() {}
};

// mixin.cpp
#include "mixin.h"

MixIn<C> も Mix-in する場合は

MixExport<MixIn<A>, Mix<MixIn<B>, Mix<MixIn<C> > > >

を public 継承すればよい.あるいは Container がクラス Interface を public 継承している場合,

MixExport<MixIn<a>, Mix<MixIn<B>, Interface> >

のようになる.Interface がある場合でのデストラクタまわりがややこしかったが, Mix テンプレートや MixExport テンプレートに仮想デストラクタを書かずとも,Container クラスが Interface クラスのポインタ経由で delete されたとしてもちゃんと Container のデストラクタが呼ばれているようだ @ gcc.

ここで注意点として,Interface クラスはその名の通り実装を持ってはならない.いや,持ってもいいのだが,持っていたとしても Interface クラスのデフォルトコンストラクタ以外のコンストラクタを呼ぶことはできない.ただこの制限は,public 継承で実装を再利用しないという方針を考えると問題ないし, 続続 pImpl × 継承 のようなやり方も使える.

もう一つは,MixIn テンプレートを実体化したクラスもデフォルトコンストラクタ以外を呼べない点だ.MixIn<A> や MixIn<B> は実装だから,この制限は問題だ.・・・のだが,うまく回避する方法が思いつかない.あまり美しくないが,コンストラクタ以外の初期化関数を定義して Container クラスのコンストラクタで呼んでやるか,素直に Container クラス内で名前の輸出をするしかなさそうだ.

追記: テンプレート引数に初期値の情報を含んだクラスを入れてやるという方法がありますね.

さて,これで十分だろうか.いやいや,問題は残っている.Java の interface はそのまま型として使える.つまり Interface インターフェイスを implement したクラスのインスタンスは, Interface 型の変数に放り込める.

上記の C++ Mix-in ではこれは不可能だ.Container クラスは Mix<MixIn<B> > として扱えるが,add や remove は呼べない.MixExport<MixIn<B> > なら add<B> と remove<B> が輸出されるが,これに Container クラスのインスタンスを入れることはできない.

というかそもそも,Container が Interface を継承しているとき,今のままでは Interface のポインタ経由で add とか remove を呼び出せない.(なにせ Interface を書いてないからね)で,どう書けばいいんじゃい.

なんとかならないだろうか(えー,もういいじゃん^^).


&trackback()

参考



コメント

  • コメントの投稿テスト -- (tossy_squirrel) 2010-12-29 03:35:18
名前:
コメント:

すべてのコメントを見る


関連ページ



関連ブログ

#blogsearch
記事メニュー
人気記事ランキング
目安箱バナー