Qual è il modo più semplice per creare e chiamare dinamicamente un metodo di classe in C ++?

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

Domanda

Voglio riempire una mappa con il nome e il metodo della classe, un identificatore univoco e un puntatore al metodo.

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

Se il metodo è statico, è sufficiente un semplice puntatore e il problema è semplice, ma voglio creare dinamicamente l'oggetto, quindi ho bisogno di memorizzare un puntatore alla classe e un offset per il metodo e non so se funziona (se l'offset è sempre lo stesso, ecc.)

Il problema è che il C ++ manca di reflection, il codice equivalente in un linguaggio interpretato con reflection dovrebbe apparire così (esempio 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: Sì, sto cercando di creare un front controller (web) MVC in C ++. So di sapere perché non usare PHP, Ruby, Python (inserisci qui la tua lingua web preferita) ecc.? Voglio solo C ++.

È stato utile?

Soluzione

Ho scritto quella roba le ultime ore e l'ho aggiunta alla mia raccolta di cose utili. La cosa più difficile è far fronte alla funzione di fabbrica, se i tipi che si desidera creare non sono in alcun modo correlati. Ho usato un boost :: variant per questo. Devi dargli una serie di tipi che vorresti mai usare. Quindi terrà traccia di ciò che è l'attuale "attivo" digitare la variante. (boost :: variant è una cosiddetta unione discriminata). Il secondo problema è come si memorizzano i puntatori a funzione. Il problema è che un puntatore a un membro di A non può essere memorizzato in un puntatore a un membro di B . Quei tipi sono incompatibili. Per risolvere questo problema, memorizzo i puntatori a funzione in un oggetto che sovraccarica il suo operatore () e accetta un boost :: variant:

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

Ovviamente, tutte le funzioni dei tuoi tipi devono avere lo stesso tipo restituito. Altrimenti l'intero gioco avrebbe poco senso. Ora il codice:

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

Utilizza tecniche piuttosto divertenti di preprocessore boost, tipi di funzione e libreria di bind. Potrebbe essere complicato, ma se ottieni le chiavi in ??quel codice, non c'è più molto da capire. Se vuoi cambiare il conteggio dei parametri, devi solo modificare variant_call_type:

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

Ora puoi chiamare le funzioni membro che accettano un int. Ecco come apparirebbe il lato chiamata:

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

Buon divertimento!


Se ora dici che quanto sopra è troppo complicato, devo essere d'accordo con te. è complicato perché C ++ non è non creato per un uso così dinamico. Se è possibile raggruppare e implementare i metodi in ciascun oggetto che si desidera creare, è possibile utilizzare funzioni virtuali pure. In alternativa, potresti lanciare qualche eccezione (come std :: runtime_error) nell'implementazione predefinita, quindi le classi derivate non devono implementare tutto:

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

Per creare oggetti, farà una normale fabbrica

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

La mappa potrebbe essere composta da un ID di mappatura della mappa su una coppia di nome di classe e funzione (lo stesso come sopra) e una mappatura della mappa su una funzione 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;

Chiamare la funzione funzionerebbe in questo modo:

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

Ovviamente, con questo approccio perdi flessibilità e (possibilmente, non hai profilato) l'efficienza, ma semplifichi notevolmente il tuo design.

Altri suggerimenti

Forse stai cercando puntatori alle funzioni membro .

Utilizzo di base:

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

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

MyClass instance;

instance.*function_ptr;

Come indicato nelle Domande frequenti Lite C ++, le macro e i typedef aumenterebbero notevolmente la leggibilità quando si usano i puntatori di funzione membro (poiché la loro sintassi non è comune nel codice).

Penso che la cosa più importante da scoprire qui sia: tutti i tuoi metodi hanno la stessa firma? Se lo fanno, questo è un uso banale di boost bind (se ti interessa), i funzione sono un'opzione (il tipo statico, tipo di anatra), o solo l'eredità virtuale semplice è un'opzione. L'ereditarietà non è attualmente in voga ma è abbastanza facile da capire e non credo che complichi più le cose quindi usando il boost bind (imho meglio per i piccoli non sistemici).

ecco un'implementazione di esempio

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

Come molte domande in C ++, questa sembra un'altra applicazione di Boost. Fondamentalmente vuoi memorizzare il risultato di boost :: bind (& amp; Class :: member, & amp; Object). [modifica] Memorizzare un tale risultato è facile con boost :: function.

Puoi provare a utilizzare i modelli di progettazione factory o abstract factory per la classe e un puntatore a funzione per la funzione.

Ho trovato le seguenti 2 pagine Web con implementazioni mentre cercavo soluzioni per un problema simile:

Factory

Fabbrica astratta

Se non si desidera utilizzare puntatori alla funzione membro , puoi usare statiche che accettano un argomento dell'istanza della classe. Ad esempio:

class MyClass
{
    public:
        void function();

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

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

Ciò richiede più lavoro sul programmatore e causa problemi di manutenibilità (poiché se si aggiorna la firma di uno, è necessario aggiornare anche quello dell'altro).

Puoi anche usare una singola funzione statica che chiama tutte le funzioni dei tuoi membri:

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

Puoi anche usare il caricamento dinamico delle funzioni:

Utilizza GetProcAddress in Windows e dlsym in Unix.

Scegli il modello di progettazione soggetto-osservatore.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top