私はそれを数時間前に書き、有用なもののコレクションに追加しました。最も難しいのは、作成したいタイプがまったく関連していない場合、ファクトリ関数に対処することです。これには a を使用しboost::variant
ました。使用したいタイプのセットを指定する必要があります。次に、バリアントで現在「アクティブな」タイプが何であるかを追跡します。(boost::variant は、いわゆる判別共用体です)。2 番目の問題は、関数ポインターを格納する方法です。問題は、 のメンバーA
へのポインターを のメンバーへのポインターに格納できないことですB
。これらのタイプは互換性がありません。operator()
これを解決するために、オーバーロードして boost::variant を取るオブジェクトに関数ポインターを格納します。
return_type operator()(variant<possible types...>)
もちろん、すべての型の関数は同じ戻り値の型を持つ必要があります。そうでなければ、ゲーム全体がほとんど意味をなさないでしょう。今コード:
#include <boost/variant.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/mpl/identity.hpp>
#include <boost/function_types/parameter_types.hpp>
#include <boost/function_types/result_type.hpp>
#include <boost/function_types/function_arity.hpp>
#include <boost/preprocessor/repetition.hpp>
#include <map>
#include <string>
#include <iostream>
// three totally unrelated classes
//
struct foo {
std::string one() {
return "I ";
}
};
struct bar {
std::string two() {
return "am ";
}
};
struct baz {
std::string three() const {
return "happy!";
}
};
// The following are the parameters you have to set
//
// return type
typedef std::string return_type;
// variant storing an object. It contains the list of possible types you
// can store.
typedef boost::variant< foo, bar, baz > variant_type;
// type used to call a function on the object currently active in
// the given variant
typedef boost::function<return_type (variant_type&)> variant_call_type;
// returned variant will know what type is stored. C++ got no reflection,
// so we have to have a function that returns the correct type based on
// compile time knowledge (here it's the template parameter)
template<typename Class>
variant_type factory() {
return Class();
}
namespace detail {
namespace fn = boost::function_types;
namespace mpl = boost::mpl;
// transforms T to a boost::bind
template<typename T>
struct build_caller {
// type of this pointer, pointer removed, possibly cv qualified.
typedef typename mpl::at_c<
fn::parameter_types< T, mpl::identity<mpl::_> >,
0>::type actual_type;
// type of boost::get we use
typedef actual_type& (*get_type)(variant_type&);
// prints _2 if n is 0
#define PLACEHOLDER_print(z, n, unused) BOOST_PP_CAT(_, BOOST_PP_ADD(n, 2))
#define GET_print(z, n, unused) \
template<typename U> \
static variant_call_type get( \
typename boost::enable_if_c<fn::function_arity<U>::value == \
BOOST_PP_INC(n), U>::type t \
) { \
/* (boost::get<actual_type>(some_variant).*t)(n1,...,nN) */ \
return boost::bind( \
t, boost::bind( \
(get_type)&boost::get<actual_type>, \
_1) BOOST_PP_ENUM_TRAILING(n, PLACEHOLDER_print, ~) \
); \
}
// generate functions for up to 8 parameters
BOOST_PP_REPEAT(9, GET_print, ~)
#undef GET_print
#undef PLACEHOLDER_print
};
}
// incoming type T is a member function type. we return a boost::bind object that
// will call boost::get on the variant passed and calls the member function
template<typename T>
variant_call_type make_caller(T t) {
return detail::build_caller<T>::template get<T>(t);
}
// actions stuff. maps an id to a class and method.
typedef std::map<std::string,
std::pair< std::string, std::string >
> actions_type;
// this map maps (class, method) => (factory, function pointer)
typedef variant_type (*factory_function)();
typedef std::map< std::pair<std::string, std::string>,
std::pair<factory_function, variant_call_type>
> class_method_map_type;
// this will be our test function. it's supplied with the actions map,
// and the factory map
std::string test(std::string const& id,
actions_type& actions, class_method_map_type& factory) {
// pair containing the class and method name to call
std::pair<std::string, std::string> const& class_method =
actions[id];
// real code should take the maps by const parameter and use
// the find function of std::map to lookup the values, and store
// results of factory lookups. we try to be as short as possible.
variant_type v(factory[class_method].first());
// execute the function associated, giving it the object created
return factory[class_method].second(v);
}
int main() {
// possible actions
actions_type actions;
actions["first"] = std::make_pair("foo", "one");
actions["second"] = std::make_pair("bar", "two");
actions["third"] = std::make_pair("baz", "three");
// connect the strings to the actual entities. This is the actual
// heart of everything.
class_method_map_type factory_map;
factory_map[actions["first"]] =
std::make_pair(&factory<foo>, make_caller(&foo::one));
factory_map[actions["second"]] =
std::make_pair(&factory<bar>, make_caller(&bar::two));
factory_map[actions["third"]] =
std::make_pair(&factory<baz>, make_caller(&baz::three));
// outputs "I am happy!"
std::cout << test("first", actions, factory_map)
<< test("second", actions, factory_map)
<< test("third", actions, factory_map) << std::endl;
}
ブースト プリプロセッサ、関数型、バインド ライブラリなどの非常に楽しい手法を使用しています。複雑なループになるかもしれませんが、そのコードでキーを取得すれば、もう把握する必要はほとんどありません。パラメータ数を変更したい場合は、variant_call_type を調整するだけです:
typedef boost::function<return_type (variant_type&, int)> variant_call_type;
これで、int を取るメンバー関数を呼び出すことができます。呼び出し側は次のようになります。
return factory[class_method].second(v, 42);
楽しむ!
上記が複雑すぎると言うなら、私はあなたに同意しなければなりません. C++ は実際にはこのような動的な使用のために作成されていないため、複雑です。作成する各オブジェクトにメソッドをグループ化して実装できる場合は、純粋仮想関数を使用できます。または、デフォルトの実装で例外 (std::runtime_error など) をスローすることもできるため、派生クラスはすべてを実装する必要はありません。
struct my_object {
typedef std::string return_type;
virtual ~my_object() { }
virtual std::string one() { not_implemented(); }
virtual std::string two() { not_implemented(); }
private:
void not_implemented() { throw std::runtime_error("not implemented"); }
};
オブジェクトを作成するには、通常のファクトリで十分です
struct object_factory {
boost::shared_ptr<my_object> create_instance(std::string const& name) {
// ...
}
};
マップは、ID をクラス名と関数名のペアにマッピングするマップ (上記と同じ) と、それを boost::function にマッピングするマップによって構成できます。
typedef boost::function<my_object::return_type(my_object&)> function_type;
typedef std::map< std::pair<std::string, std::string>, function_type>
class_method_map_type;
class_method_map[actions["first"]] = &my_object::one;
class_method_map[actions["second"]] = &my_object::two;
関数の呼び出しは次のようになります。
boost::shared_ptr<my_object> p(get_factory().
create_instance(actions["first"].first));
std::cout << class_method_map[actions["first"]](*p);
もちろん、このアプローチでは、柔軟性と (プロファイルを作成していない可能性があります) 効率が失われますが、設計は大幅に簡素化されます。