在C ++中动态创建和动态调用类方法的最简单方法是什么? [英] What is the simplest way to create and call dynamically a class method in C++?

查看:79
本文介绍了在C ++中动态创建和动态调用类方法的最简单方法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想用类名和方法,唯一标识符以及指向该方法的指针填充地图.

I want to fill a map with class name and method, a unique identifier and a pointer to the method.

typedef std::map<std::string, std::string, std::string, int> actions_type;
typedef actions_type::iterator actions_iterator;

actions_type actions;
actions.insert(make_pair(class_name, attribute_name, identifier, method_pointer));

//after which I want call the appropriate method in the loop

while (the_app_is_running)
{
    std::string requested_class = get_requested_class();
    std::string requested_method = get_requested_method();

    //determine class
    for(actions_iterator ita = actions.begin(); ita != actions.end(); ++ita)
    {
        if (ita->first == requested_class && ita->second == requested_method)
        {
            //class and method match
            //create a new class instance
            //call method
        }
    }
}

如果该方法是静态的,那么简单的指针就足够了,问题很简单, 但是我想动态创建对象,因此我需要存储指向类的指针和方法的偏移量,并且我不知道这样做是否可行(如果偏移量始终相同,等等).

If the method is static then a simple pointer is enough and the problem is simple, but I want to dynamically create the object so I need to store a pointer to class and an offset for the method and I don't know if this works (if the offset is always the same etc).

问题是C ++缺少反射,带有反射的解释语言中的等效代码应如下所示(PHP中的示例):

The problem is that C++ lacks reflection, the equivalent code in a interpreted language with reflection should look like this (example in PHP):

$actions = array
(
     "first_identifier" => array("Class1","method1"),
     "second_identifier" => array("Class2","method2"),
     "third_identifier" => array("Class3","method3")
);

while ($the_app_is_running)
{
     $id = get_identifier();

     foreach($actions as $identifier => $action)
     {
         if ($id == $identifier)
         {
             $className = $action[0];
             $methodName = $action[1];

             $object = new $className() ;

             $method = new ReflectionMethod($className , $methodName);
             $method -> invoke($object);    
         }
     }
 }

PS:是的,我正在尝试使用C ++创建一个(Web)MVC前端控制器. 我知道我知道为什么不使用PHP,Ruby,Python(在这里插入您喜欢的网络语言)等吗,我只想要C ++.

PS: Yes I'm trying to make a (web) MVC front controller in C++. I know I know why don't use PHP, Ruby, Python (insert your favorite web language here) etc?, I just want C++.

推荐答案

我上个小时写了这些东西,并将其添加到我的有用东西集合中.如果您要创建的类型没有任何关联,那么最困难的事情就是应对工厂功能.我为此使用了boost::variant.您必须为它提供一组您想使用的类型.然后,它将跟踪变体中当前的活动"类型是什么. (boost :: variant是所谓的有区别的联合).第二个问题是如何存储函数指针.问题是指向A的成员的指针不能存储到指向B的成员的指针.这些类型不兼容.为了解决这个问题,我将函数指针存储在一个对象中,该对象重载了operator()并使用boost :: variant:

I wrote that stuff last hours, and added it to my collection of useful stuff. The most difficult thing is to cope with the factory function, if the types you want to create are not related in any way. I used a boost::variant for this. You have to give it a set of types you ever want to use. Then it will keep track what is the current "active" type in the variant. (boost::variant is a so-called discriminated union). The second problem is how you store your function pointers. The problem is that a pointer to a member of A can't be stored to a pointer to a member of B. Those types are incompatible. To solve this, i store the function pointers in an object that overloads its operator() and takes a boost::variant:

return_type operator()(variant<possible types...>)

当然,所有类型的函数都必须具有相同的返回类型.否则,整个游戏将毫无意义.现在的代码:

Of course, all your types' functions have to have the same return type. Otherwise the whole game would only make little sense. Now the code:

#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;
}

它使用了来自boost预处理器,函数类型和绑定库的非常有趣的技术.可能循环很复杂,但是如果您在该代码中获得了键,就不再需要太多了.如果要更改参数计数,只需调整variant_call_type:

It uses pretty fun techniques from boost preprocessor, function types and bind library. Might loop complicated, but if you get the keys in that code, it's not much to grasp anymore. If you want to change the parameter count, you just have to tweak variant_call_type:

typedef boost::function<return_type (variant_type&, int)> variant_call_type;

现在,您可以调用带有int的成员函数.呼叫方的外观如下:

Now you can call member functions that take an int. Here is how the call side would look:

return factory[class_method].second(v, 42);

玩得开心!

如果您现在说上述内容太复杂,我必须同意您的看法. 很复杂,因为C ++实际上不是为这种动态用途而制作的.如果可以在要创建的每个对象中对方法进行分组和实现,则可以使用纯虚函数.另外,您可以在默认实现中引发一些异常(如std :: runtime_error),因此派生类无需实现所有事情:

If you now say the above is too complicated, i have to agree with you. It is complicated because C++ is not really made for such dynamic use. If you can have your methods grouped and implemented in each object you want create, you can use pure virtual functions. Alternatively, you could throw some exception (like std::runtime_error) in the default implementation, so derived classes do not need to implement everything:

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"); }
};

对于创建对象,通常的工厂会做

For creating objects, a usual factory will do

struct object_factory {
    boost::shared_ptr<my_object> create_instance(std::string const& name) {
        // ...
    }
};

该映射可以由映射ID映射到一对类和函数名称的映射(与上面相同),以及映射到boost :: function:

The map could be composed by a map mapping IDs to a pair of class and function name (the same like above), and a map mapping that to a 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);

当然,通过这种方法,您失去了灵活性和(可能没有介绍)效率,但是却大大简化了设计.

Of course, with this approach, you loose flexibility and (possibly, haven't profiled) efficiency, but you greatly simplify your design.

这篇关于在C ++中动态创建和动态调用类方法的最简单方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆