Какой самый простой способ динамически создавать и вызывать метод класса в C ++? - PullRequest
3 голосов
/ 01 января 2009

Я хочу заполнить карту именем класса и методом, уникальным идентификатором и указателем на метод.

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

Если метод статический, то достаточно простого указателя и проблема проста, но я хочу динамически создать объект, поэтому мне нужно сохранить указатель на класс и смещение для метода, и я не знаю, работает ли это (если смещение всегда одинаковое и т. д.).

Проблема в том, что в C ++ отсутствует отражение, эквивалентный код в интерпретируемом языке с отражением должен выглядеть следующим образом (пример в 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: Да, я пытаюсь сделать (web) фронт-контроллер MVC на C ++. Я знаю, я знаю, почему не используют PHP, Ruby, Python (вставьте ваш любимый веб-язык здесь) и т. Д., Я просто хочу C ++.

Ответы [ 8 ]

7 голосов
/ 01 января 2009

Возможно, вы ищете указатели на функции-члены .

Основное использование:

class MyClass
{
    public:
        void function();
};

void (MyClass:*function_ptr)() = MyClass::function;

MyClass instance;

instance.*function_ptr;

Как указано в C ++ FAQ Lite, макросы и typedef s значительно улучшат читабельность при использовании указателей на функции-члены (потому что их синтаксис не распространен в коде).

5 голосов
/ 02 января 2009

Я написал это в последние часы и добавил в свою коллекцию полезных вещей. Сложнее всего справиться с заводской функцией, если типы, которые вы хотите создать, никак не связаны между собой. Я использовал boost::variant для этого. Вы должны дать ему набор типов, которые вы когда-либо захотите использовать. Затем он будет отслеживать текущий «активный» тип в варианте. (boost :: variable - это так называемый дискриминационный союз). Вторая проблема заключается в том, как вы храните свои указатели функций. Проблема в том, что указатель на член A не может быть сохранен на указатель на член B. Эти типы несовместимы. Чтобы решить эту проблему, я храню указатели на функции в объекте, который перегружает его operator() и принимает boost :: variable:

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

Он использует довольно забавные приемы из препроцессора boost, типов функций и библиотеки bind. Может быть сложным цикл, но если вы получите ключи в этом коде, это уже не так много для понимания. Если вы хотите изменить количество параметров, вам просто нужно настроить вариант_переменный_тип:

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) {
        // ...
    }
};

Карта может быть составлена ​​из идентификаторов сопоставления карты с парой имени класса и функции (аналогично описанному выше) и сопоставления карты с 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);

Конечно, при таком подходе вы теряете гибкость и (возможно, не профилировали) эффективность, но вы значительно упрощаете свой дизайн.

2 голосов
/ 02 января 2009

Я думаю, что самая важная вещь, которую необходимо выяснить, это то, что все ваши методы имеют одинаковую сигнатуру? Если они это делают, это тривиальное использование boost bind (если вам это нравится), функторы являются опцией (статический тип типа утки), или просто обычное виртуальное наследование ole является опцией. Наследование в настоящее время не в моде, но его довольно легко понять, и я не думаю, что оно усложняет ситуацию больше, чем использование boost bind (imho лучше всего подходит для небольших несистемных функторов).

вот пример реализации

#include<iostream>
#include<map>
#include<string>

using std::map;
using std::string;
using std::cout;
using std::pair;

class MVCHandler
{
public:
    virtual void operator()(const string& somekindofrequestinfo) = 0;
};

class MyMVCHandler : public MVCHandler
{
public:
    virtual void operator()(const string& somekindofrequestinfo)
    {
        cout<<somekindofrequestinfo;
    }
};

void main()
{
    MyMVCHandler myhandler;
    map<string, MVCHandler*> handlerMap;
    handlerMap.insert(pair<string, MVCHandler*>("mysuperhandler", &myhandler));
    (*handlerMap["mysuperhandler"])("somekindofrequestdata");
}
2 голосов
/ 02 января 2009

Как и многие вопросы C ++, это похоже на другое приложение Boost. Вы в основном хотите сохранить результат boost :: bind (& Class :: member, & Object). [править] Сохранить такой результат легко с помощью boost :: function.

1 голос
/ 01 января 2009

Если вы не хотите использовать указатели на функции-члены , вы можете использовать статику, которая принимает аргумент экземпляра класса. Например:

class MyClass
{
    public:
        void function();

        static void call_function(MyClass *instance);  // Or you can use a reference here.
};

MyClass instance;
MyClass::call_function(&instance);

Это требует больше работы над кодером и вызывает проблемы с ремонтопригодностью (так как если вы обновляете подпись одного, вы должны также обновить подпись другого).

Вы также можете использовать одну статическую функцию, которая вызывает все ваши функции-члены:

class MyClass
{
    public:
        enum Method
        {
            fp_function,
        };

        void function();

        static void invoke_method(MyClass *instance, Method method);  // Or you can use a reference here.
};

void MyClass::invoke_method(MyClass *instance, Method method)
{
    switch(method)
    {
        default:
            // Error or something here.
            return;

        case fp_function:
            instance->function();
            break;

        // Or, if you have a lot of methods:

#define METHOD_CASE(x) case fp_##x: instance->x(); break;

        METHOD_CASE(function);

#undef METHOD_CASE
    }

    // Free logging!  =D
}

MyClass instance;
MyClass::invoke_method(instance, MyClass::fp_function);
1 голос
/ 01 января 2009

Можно попробовать использовать фабричные или абстрактные фабричные шаблоны проектирования для класса и указатель на функцию для функции.

Я нашел следующие 2 веб-страницы с реализациями, когда искал решения для аналогичной проблемы:

Завод

Абстрактная фабрика

0 голосов
/ 02 января 2009

Перейти для шаблона проектирования Subject-Observer.

0 голосов
/ 02 января 2009

Вы также можете использовать динамическую загрузку функций:

Используйте GetProcAddress в Windows и dlsym в Unix.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...