What is the simplest way to create and call dynamically a class method in C++?

后端 未结 8 1086
太阳男子
太阳男子 2021-01-01 05:28

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

typedef std::map

        
8条回答
  •  有刺的猬
    2021-01-01 06:35

    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)
    

    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 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    // 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 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
    variant_type factory() {
        return Class();
    }
    
    namespace detail {
    namespace fn = boost::function_types;
    namespace mpl = boost::mpl;
    
    // transforms T to a boost::bind
    template
    struct build_caller {
        // type of this pointer, pointer removed, possibly cv qualified. 
        typedef typename mpl::at_c<
            fn::parameter_types< T, mpl::identity >,
            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                                                \
        static variant_call_type get(                                       \
            typename boost::enable_if_c::value ==     \
                BOOST_PP_INC(n), U>::type t                                 \
            ) {                                                             \
            /* (boost::get(some_variant).*t)(n1,...,nN) */     \
            return boost::bind(                                             \
                t, boost::bind(                                             \
                    (get_type)&boost::get,                     \
                    _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
    variant_call_type make_caller(T t) {
        return detail::build_caller::template get(t);
    }
    
    // actions stuff. maps an id to a class and method.
    typedef std::map
                     > actions_type;
    
    // this map maps (class, method) => (factory, function pointer)
    typedef variant_type (*factory_function)();
    typedef std::map< std::pair, 
                      std::pair 
                      > 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 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, make_caller(&foo::one));
        factory_map[actions["second"]] = 
            std::make_pair(&factory, make_caller(&bar::two));
        factory_map[actions["third"]] = 
            std::make_pair(&factory, 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;
    }
    

    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 variant_call_type;
    

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

    Have fun!


    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 create_instance(std::string const& name) {
            // ...
        }
    };
    

    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 function_type;
    typedef std::map< std::pair, function_type> 
                      class_method_map_type;
    class_method_map[actions["first"]] = &my_object::one;
    class_method_map[actions["second"]] = &my_object::two;
    

    Calling the function would work like this:

    boost::shared_ptr 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.

提交回复
热议问题