我在前几个小时写了这些东西,并将其添加到我的有用工具集中。最困难的是处理工厂函数,如果你想要创建的类型没有任何关联。我使用了 boost::variant
来解决这个问题。你必须给它一组你想要使用的类型。然后它会跟踪变体中当前“活动”的类型。(boost::variant 是所谓的带标签联合)。第二个问题是如何存储函数指针。问题是无法将指向 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>
struct foo {
std::string one() {
return "I ";
}
};
struct bar {
std::string two() {
return "am ";
}
};
struct baz {
std::string three() const {
return "happy!";
}
};
typedef std::string return_type;
typedef boost::variant< foo, bar, baz > variant_type;
typedef boost::function<return_type (variant_type&)> variant_call_type;
template<typename Class>
variant_type factory() {
return Class();
}
namespace detail {
namespace fn = boost::function_types;
namespace mpl = boost::mpl;
template<typename T>
struct build_caller {
typedef typename mpl::at_c<
fn::parameter_types< T, mpl::identity<mpl::_> >,
0>::type actual_type;
typedef actual_type& (*get_type)(variant_type&);
#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 \
) { \
\
return boost::bind( \
t, boost::bind( \
(get_type)&boost::get<actual_type>, \
_1) BOOST_PP_ENUM_TRAILING(n, PLACEHOLDER_print, ~) \
); \
}
BOOST_PP_REPEAT(9, GET_print, ~)
#undef GET_print
#undef PLACEHOLDER_print
};
}
template<typename T>
variant_call_type make_caller(T t) {
return detail::build_caller<T>::template get<T>(t);
}
typedef std::map<std::string,
std::pair< std::string, std::string >
> actions_type;
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;
std::string test(std::string const& id,
actions_type& actions, class_method_map_type& factory) {
std::pair<std::string, std::string> const& class_method =
actions[id];
variant_type v(factory[class_method].first());
return factory[class_method].second(v);
}
int main() {
actions_type actions;
actions["first"] = std::make_pair("foo", "one");
actions["second"] = std::make_pair("bar", "two");
actions["third"] = std::make_pair("baz", "three");
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));
std::cout << test("first", actions, factory_map)
<< test("second", actions, factory_map)
<< test("third", actions, factory_map) << std::endl;
}
这段代码使用了boost preprocessor、function types和bind library等技术。可能看起来比较复杂,但只要掌握了其中的关键,就不难理解了。如果你想改变参数数量,只需要调整variant_call_type即可。
typedef boost::function<return_type (variant_type&, int)> variant_call_type;
现在您可以调用需要一个整数参数的成员函数。以下是调用示例:
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);
当然,采用这种方法,您会失去灵活性和(可能,未进行过剖析)效率,但可以极大地简化设计。