Каков самый простой способ создания и динамического вызова метода класса в C++?
-
03-07-2019 - |
Вопрос
Я хочу заполнить карту именем класса и методом, уникальным идентификатором и указателем на метод.
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);
}
}
}
ПС:Да, я пытаюсь создать (веб) фронт-контроллер MVC на C++.Я знаю, почему бы не использовать PHP, Ruby, Python (вставьте сюда свой любимый веб-язык) и т. д.? Мне просто нужен C++.
Решение
Я написал это в последние часы и добавил в свою коллекцию полезных вещей.Сложнее всего справиться с фабричной функцией, если типы, которые вы хотите создать, никак не связаны между собой.Я использовал boost::variant
для этого.Вы должны предоставить ему набор типов, которые вы когда-либо захотите использовать.Затем он будет отслеживать текущий «активный» тип в варианте.(boost::variant — это так называемый дискриминируемый союз).Вторая проблема заключается в том, как хранить указатели на функции.Проблема в том, что указатель на член A
не может быть сохранен в указателе на член B
.Эти типы несовместимы.Чтобы решить эту проблему, я сохраняю указатели функций в объекте, который перегружает его. operator()
и принимает импульс::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;
}
Он использует довольно забавные методы из препроцессора Boost, типов функций и библиотеки привязки.Цикл может быть сложным, но если вы получите ключи в этом коде, в нем больше нечего будет понимать.Если вы хотите изменить количество параметров, вам просто нужно настроить вариант_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) {
// ...
}
};
Карта может состоять из идентификаторов сопоставления карты с парой имени класса и функции (так же, как указано выше), а также сопоставления карты с 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);
Конечно, при таком подходе вы теряете гибкость и (возможно, не профилировали) эффективность, но значительно упрощаете свою конструкцию.
Другие советы
Возможно, вы ищете указатели на функции-члены.
Основное использование:
class MyClass
{
public:
void function();
};
void (MyClass:*function_ptr)() = MyClass::function;
MyClass instance;
instance.*function_ptr;
Как указано в C++ FAQ Lite, макросы и typedef
s значительно повысит читаемость при использовании указателей на функции-члены (поскольку их синтаксис не распространен в коде).
Я думаю, самое важное здесь выяснить, все ли ваши методы имеют одинаковую сигнатуру?Если да, то это тривиальное использование привязки boost (если вы в этом заинтересованы), функторы являются опцией (статические, типа утки) или просто виртуальным наследованием.Наследование в настоящее время не в моде, но его довольно легко понять, и я не думаю, что оно больше усложняет ситуацию, чем использование привязки повышения (по моему мнению, лучше всего подходит для небольших несистемных функторов).
вот пример реализации
#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");
}
Как и многие вопросы по C++, это похоже на еще одно применение Boost.По сути, вы хотите сохранить результат boost::bind(&Class::member, &Object).[edit] Сохранить такой результат легко с помощью boost::function.
Вы можете попробовать использовать фабричные или абстрактные фабричные шаблоны проектирования для класса и указатель на функцию для функции.
Когда я искал решения аналогичной проблемы, я нашел следующие две веб-страницы с реализациями:
Если вы не хотите использовать указатели на функции-члены, вы можете использовать статику, которая принимает аргумент экземпляра класса.Например:
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);
Вы также можете использовать динамическую загрузку функций:
Используйте GetProcAddress в Windows и dlsym в Unix.
Используйте шаблон проектирования «Субъект-наблюдатель».