ホーム   最近   SikiWiki   編集   新規 

[C++プログラミング手法]

動的型変数とマルチメソッド

2009/01/12_191240




意図

動的型変数と、その変数を引数とするマルチメソッドを提供する。

動機

C++では、静的型のチェックによりコンパイル時にプログラムの整合性を判定できるようになっている。しかし、全ての型がコンパイル時にチェックされるため、徐々に機能を追加していく開発スタイル(スクリプト言語などで良く行われている)だと、この厳しい型チェックのせいで柔軟なプログラミングが困難となっている。

この問題を解決するため、C++ライブラリの一つであるboostでは、動的型変数boost::anyを用意している。しかし、boost::anyはその変数を処理するときに保存してあるデータの型を正確に指定する必要があり、柔軟性が高いとは言えない。

また、Modern C++ Designでは動的に実行するメンバ関数を変更するマルチメソッドの技法について言及しているが、最高速の定数時間のマルチメソッドを実現する手法はクラスそのものに手を加える必要があり、汎用性に劣っていた。

そこで、この二つの技法を組み合せることにより、汎用性が高く、また速度的にも優れた動的変数/マルチメソッドを実装する。

解法とサンプルコード

boost::anyに、型ごとの番号付けの手法を使用してナンバリングを行う機能を付加し、Modern C++ Designで言及されている最高速の定数時間のマルチメソッドを実装する。なお、下記の実装は単純化しているため、詳細はこのページを参照のこと。

なお、Modern C++ Designの対数時間のマルチメソッドならば、boost::anyをそのまま流用してマルチメソッドを実装することが可能である。

// Holder.hpp struct MultiMethod { MultiMethod() {}; public: template<typename R, typename M, typename H, typename A1> static bool entry() { struct Local { static R trampoline(M& self, H& arg1) { // (b) return self(holder_cast<A1>(arg1)); }; }; typedef R (*T)(M&, H&); static bool t((find<T>(Holder::id<A1>()) = &Local::trampoline) != 0); return true; }; template<typename R, typename M, typename H> static R apply(M& method, H& arg1) { typedef R (*T)(M&, H&); T t(find<T>(arg1.id())); if (!t) throw BadMultiMethod(); return t(method, arg1); }; private: //////////////////////////////////////////////////////////////////////// template<typename T> static T& find(const unsigned int pos1) { static std::vector<T> r; // (a) if (r.size() <= pos1) { r.resize(pos1+1, 0); } return r[pos1]; }; }; /////////////////////////////////////////////////////////////////////////// // Original any for fast multi method /////////////////////////////////////////////////////////////////////////// class Holder { public: Holder() : caddy_(0) {}; template<typename type_t> Holder(const type_t& value) : caddy_(new Container<type_t>(value)) {}; Holder(const Holder& other) : caddy_(other.caddy_ ? other.caddy_->clone() : 0) {}; ~Holder() { delete caddy_; }; Holder& swap(Holder& other) { std::swap(caddy_, other.caddy_); return *this; }; template<typename type_t> Holder& operator=(const type_t& other) { Holder(other).swap(*this); return *this; }; Holder& operator=(const Holder& other) { Holder(other).swap(*this); return *this; }; bool empty() const { return !caddy_; }; const std::type_info& type() const { return caddy_ ? caddy_->type() : typeid(void); }; template<typename type_t> type_t* cast() { return caddy_ && type() == typeid(type_t) ? &static_cast<Container<type_t> *>(caddy_)->content_ : 0; }; // additional method unsigned int id() { return caddy_ ? caddy_->id() : 0; }; template<typename type_t> static unsigned int id() { static unsigned int r(count()); return r; }; private: static unsigned int count() { static unsigned int r(0); return ++r; }; // end class Caddy { public: virtual ~Caddy() {}; virtual const std::type_info& type() const = 0; virtual Caddy* clone() const = 0; virtual unsigned int id() = 0; // additional method }; template<typename type_t> class Container : public Caddy { public: Container(const type_t& value) : content_(value) {}; virtual const std::type_info& type() const { return typeid(type_t); }; virtual Container* clone() const { return new Container(content_); }; virtual unsigned int id() { return Holder::id<type_t>(); }; // additional method type_t content_; }; Caddy* caddy_; }; class bad_holder_cast : public std::bad_cast { public: virtual const char * what() const throw() { return "bad_holder_cast: failed conversion using holder_cast"; }; }; template<typename type_t> type_t* holder_cast(Holder* operand) { return operand->cast<type_t>(); }; template<typename type_t> inline const type_t* holder_cast(const Holder * operand) { return holder_cast<type_t>(const_cast<Holder *>(operand)); }; template<typename type_t> type_t holder_cast(Holder& operand) { typedef typename remove_reference<type_t>::type nonref; nonref* result = holder_cast<nonref>(&operand); if(!result) boost::throw_exception(bad_holder_cast()); return *result; }; template<typename type_t> inline type_t holder_cast(const Holder& operand) { return holder_cast<const remove_reference<type_t>::type&>(const_cast<Holder&>(operand)); }; // main.cpp struct TestMethod { std::string operator()(Holder& value) { return MultiMethod::apply<string>(value); }; //(1) std::string operator()(int value) const { return string("int1"); }; //(2) std::string operator()(double value) const { return string("double1"); }; //(3) }; bool m00(MultiMethod::entry<std::string, TestMethod, Holder, int>()); bool m01(MultiMethod::entry<std::string, TestMethod, Holder, double>()); //(4) int main() { TestMethod0 m; Holder h0(0); m(h0); //(5)"int0" Holder h1(0.0); m(h1); //(6)"double0" h1 = 0; m(h1); //(7)"int0" h1 = 'a'; m(h1); //(8)"int0" };

Holderがboost::anyを改造した動的変数クラスで、MultiMethodクラスの静的メンバ関数がマルチメソッド用の関数群である(クラス化してグループ化・アクセス制御を行っている)。使い方は上記の通り。

Holderはboost::anyに型ごとの番号付けを行うメンバ関数idを追加したものである。定数時間のマルチメソッドでは、配列のインデックスアクセスを使用してメソッドへの定数時間アクセスを実現しているが、このマルチメソッドでも同様の技法を使用して定数時間アクセスを実現している。(なお、boost::anyの詳細は説明しない)

MultiMethod::findは、Holderに保存されたインスタンスを実際の型にキャストして実行するトランポリン関数(b)を要素とする配列(a)を、静的ローカル変数として保持する。この配列(a)は、(Holder::idにより型ごとに割り当てられた)整数をインデックスとしている。マルチメソッドは、このトランポリン関数を実行時に呼び出し、引数のHolderを実際の型に変換した上で関数を呼び出すことで実現している。

(なお、上記の配列(a)はコンパイル時に自動的に初期化することができないため、(4)のように登録用のメソッドを使用して初期化を行う)

TestMethodが今回対象となるマルチメソッドで、(2)及び(3)が動的に呼び出されるメソッドである。なお、本例では(1)のように転送関数を用意し、通常の関数オブジェクトと同様にマルチメソッドを呼び出すことができるようにしている。

実際に使用するときは、(5)-(8)のように関数オブジェクトとして使用する。メソッドの呼び出しは

  1. (1)の転送関数でMultiMethod::applyに渡す。
  2. MultiMethod::applyは、
    1. 引数のHolderからHolder::id(実体はContainer::id)を使用して、型ごとに割り当てられた整数を入手
    2. 上記整数をMultiMethod::findに渡して該当するトランポリン関数を入手する。
    3. トランポリン関数内で、Holderを実際の型に変換した上で再度関数オブジェクトを実行する。

の手順で、実行時に引数によるディスパッチを行う。





制作・著作: 野分(nowake) at fiercewinds.net (Creative Commons 表示-継承 2.1 日本)