¿Cuál es la forma más sencilla de crear y llamar dinámicamente un método de clase en C ++?

StackOverflow https://stackoverflow.com/questions/405432

Pregunta

Quiero llenar un mapa con el nombre de la clase y el método, un identificador único y un puntero al método.

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

Si el método es estático, un simple puntero es suficiente y el problema es simple, pero quiero crear dinámicamente el objeto, así que necesito almacenar un puntero a la clase y un desplazamiento para el método y no sé si esto funciona (si el desplazamiento es siempre el mismo, etc.).

El problema es que C ++ carece de reflexión, el código equivalente en un lenguaje interpretado con reflexión debería verse así (ejemplo en 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);    
         }
     }
 }

PD: Sí, estoy tratando de hacer un controlador frontal MVC (web) en C ++. Sé que sé por qué no usar PHP, Ruby, Python (inserta tu idioma web favorito aquí, etc.), solo quiero C ++.

¿Fue útil?

Solución

Escribí esas cosas las últimas horas y las agregué a mi colección de cosas útiles. Lo más difícil es hacer frente a la función de fábrica, si los tipos que desea crear no están relacionados de ninguna manera. Utilicé una boost :: variant para esto. Tienes que darle un conjunto de tipos que quieras usar. Luego hará un seguimiento de cuál es el activo actual. Escriba la variante. (boost :: variant es una llamada unión discriminada). El segundo problema es cómo almacena sus punteros de función. El problema es que un puntero a un miembro de A no puede almacenarse en un puntero a un miembro de B . Esos tipos son incompatibles. Para resolver esto, almaceno los punteros de función en un objeto que sobrecarga su operator () y toma una variante boost :: variante:

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

Por supuesto, todas las funciones de sus tipos deben tener el mismo tipo de retorno. De lo contrario, todo el juego solo tendría poco sentido. Ahora el código:

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

Utiliza técnicas bastante divertidas de preprocesador de impulso, tipos de funciones y biblioteca de enlaces. Puede que el ciclo sea complicado, pero si obtienes las claves en ese código, ya no es mucho para entender. Si desea cambiar el recuento de parámetros, solo tiene que ajustar variant_call_type:

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

Ahora puede llamar a funciones miembro que toman un int. Así es como se vería el lado de la llamada:

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

¡Diviértete!


Si ahora dice que lo anterior es demasiado complicado, tengo que estar de acuerdo con usted. es complicado porque C ++ es no realmente hecho para un uso tan dinámico. Si puede agrupar e implementar sus métodos en cada objeto que desea crear, puede usar funciones virtuales puras. Alternativamente, podría lanzar alguna excepción (como std :: runtime_error) en la implementación predeterminada, por lo que las clases derivadas no necesitan implementar todo:

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

Para crear objetos, una fábrica habitual servirá

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

El mapa podría estar compuesto por un ID de mapeo de mapa a un par de clase y nombre de función (el mismo que el anterior), y un mapeo de mapa que a una función boost :::

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;

Llamar a la función funcionaría así:

boost::shared_ptr<my_object> p(get_factory().
    create_instance(actions["first"].first));
std::cout << class_method_map[actions["first"]](*p);

Por supuesto, con este enfoque, pierde flexibilidad y (posiblemente, no ha perfilado) eficiencia, pero simplifica enormemente su diseño.

Otros consejos

Quizás esté buscando punteros de función de miembro .

Uso básico:

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

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

MyClass instance;

instance.*function_ptr;

Como se indica en C ++ FAQ Lite, las macros y typedef s aumentarían enormemente la legibilidad al usar punteros de funciones miembro (porque su sintaxis no es común en el código).

Creo que lo más importante para descubrir aquí es, ¿todos sus métodos tienen la misma firma? Si lo hacen, este es un uso trivial de boost bind (si te gusta eso), los functors son una opción (el tipo estático, tipo pato), o simplemente la herencia virtual simple es una opción. La herencia no está actualmente en boga, pero es bastante fácil de entender y no creo que complique más las cosas que usar boost bind (en mi opinión, lo mejor para los pequeños no sistémicos).

aquí hay una implementación de muestra

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

Como muchas preguntas de C ++, esta parece otra aplicación de Boost. Básicamente, desea almacenar el resultado de boost :: bind (& amp; Class :: member, & amp; Object). [editar] Almacenar este resultado es fácil con la función boost ::.

Puede intentar usar patrones de diseño de fábrica o abstractos de fábrica para la clase, y un puntero de función para la función.

Encontré las siguientes 2 páginas web con implementaciones cuando estaba buscando soluciones para un problema similar:

Factory

Fábrica abstracta

Si no desea utilizar punteros de funciones miembro , puede usar estadísticas que toman un argumento de la instancia de clase. Por ejemplo:

class MyClass
{
    public:
        void function();

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

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

Esto requiere más trabajo en el codificador y provoca problemas de mantenimiento (ya que si actualiza la firma de uno, también debe actualizar la del otro).

También podría usar una única función estática que llame a todas sus funciones miembro:

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

También puede usar la carga dinámica de las funciones:

Use GetProcAddress en Windows y dlsym en Unix.

Elija el patrón de diseño de Observador de sujeto.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top