2010-01-27 23 views
9

Ho creato una classe Timer che deve chiamare un metodo di richiamata quando il timer è scaduto. Attualmente lo faccio funzionare con normali puntatori di funzione (sono dichiarati come void (*) (void), quando si verifica l'evento Elapsed, viene chiamato il puntatore della funzione.Come definire un puntatore di funzione membro generale

È possibile fare la stessa cosa con una funzione membro che ha anche il vuoto firma (AnyClass :: *) (void)

Grazie compagni

EDIT:?. Questo codice deve lavorare su Windows e anche su un real-time OS (VxWorks) in modo da non utilizzare librerie esterne

EDIT2: Solo per essere sicuro, quello che mi serve è avere una classe Timer che possa discutere con il Costruttore di tipo "AnyClass.AnyMethod" senza argomenti e restituzione void. Devo memorizzare questo argomento e quest'ultimo in un punto del codice esegue semplicemente il metodo puntato da questa variabile. La speranza è chiara

+0

Sei sicuro che il puntatore della funzione non è dichiarato come 'void (*) (void *)'. Sarei estremamente scioccato se non accettasse un "void *" come parametro. –

risposta

8

Dipendenze, dipendenze ... sì, boost sicuro è bello, così è mem_fn, ma non ne hai bisogno. Tuttavia, la sintassi di chiamare funzioni membro è il male, quindi un po 'di magia modello consente di:

class Callback 
    { 
    public: 
     void operator()() { call(); }; 
     virtual void call() = 0; 
    }; 

    class BasicCallback : public Callback 
    { 
     // pointer to member function 
     void (*function)(void); 
    public: 
     BasicCallback(void(*_function)(void)) 
      : function(_function) { }; 
     virtual void call() 
     { 
      (*function)(); 
     }; 
    }; 

    template <class AnyClass> 
    class ClassCallback : public Callback 
    { 
     // pointer to member function 
     void (AnyClass::*function)(void); 
     // pointer to object 
     AnyClass* object;   
    public: 
     ClassCallback(AnyClass* _object, void(AnyClass::*_function)(void)) 
      : object(_object), function(_function) { }; 
     virtual void call() 
     { 
      (*object.*function)(); 
     }; 
    }; 

Ora si può semplicemente utilizzare richiamata come un meccanismo di memorizzazione di richiamata così:

void set_callback(Callback* callback); 
set_callback(new ClassCallback<MyClass>(my_class, &MyClass::timer)); 

E

Callback* callback = new ClassCallback<MyClass>(my_class, &MyClass::timer)); 

(*callback)(); 
// or... 
callback->call(); 
+0

Ok, ricordati che qui non capisco quasi nulla. C++ non è quello che uso per lavorare, ho solo bisogno di fare alcune modifiche a un progetto in esecuzione, quindi mi aspetto di evitare di comprendere tutta questa magia. Come posso eseguire la richiamata? Se metto semplicemente callback(); ottengo un termine non valuta una funzione che accetta 0 argomenti. Grazie in anticipo. –

+0

E la classe BasicCallback sembra non essere utilizzata da nessuna parte. Forse c'è un errore lì? –

+0

@SoMoS, BasicCallback ti consente di utilizzare la stessa interfaccia con i tuoi vecchi callback - non accetta una classe. Hai ricevuto il tuo errore perché hai provato a "eseguire" un puntatore, '(* callback)()' sarebbe la chiamata corretta. Ho modificato un po 'il mio codice e aggiunto un metodo 'call' esplicito, forse quello sarà più chiaro. –

4

La soluzione migliore che ho utilizzato per lo stesso scopo era boost::signal o boost::function librerie (a seconda se si desidera una singola richiamata o molti di loro), e boost::bind per registrare in realtà i callback.

class X { 
public: 
    void callback() {} 
    void with_parameter(std::string const & x) {} 
}; 
int main() 
{ 
    X x1, x2; 
    boost::function< void() > callback1; 

    callback1 = boost::bind(&X::callback, &x1); 
    callback1(); // will call x1.callback() 

    boost::signal< void() > multiple_callbacks; 
    multiple_callbacks.connect(boost::bind(&X::callback, &x1)); 
    multiple_callbacks.connect(boost::bind(&X::callback, &x2)); 
    // even inject parameters: 
    multiple_callbacks.connect(boost::bind(&X::with_parameter, &x1, "Hi")); 

    multiple_callbacks(); // will call x1.callback(), x2.callback and x1.with_parameter("Hi") in turn 
} 
+1

Ma non potenzia una libreria esterna? – futureelite7

+4

Sì, non fanno parte dello standard, ma boost è la libreria standard non standard che troverete. –

+1

Inoltre, 'function' e' bind' saranno aggiunti alla libreria standard di C++ 0x (potrebbe essere già disponibile nello spazio dei nomi 'std :: tr1'). – UncleBens

1

boost::function sembra perfetto.

+0

Ha detto che non ci sono librerie esterne. –

+1

Oh, mi mancava :) Si noti che la funzione boost :: è solo libreria di intestazione, quindi nessuna dipendenza di collegamento. –

2

Forse lo standard mem_fun è già abbastanza buono per quello che vuoi. Fa parte di STL.

+0

Fammi controllare. Sembra buono. –

+0

Ho proposto la funzione boost :: come stavo assumendo che il Timer fosse una classe non basata su modelli che doveva mantenere la callback per un uso successivo. Dato che mem_fun è un template, puoi modificare la classe del timer nella classe del callback, altrimenti dovrai avvolgere il mem_fun in una classe non-templated, ed ecco dove finisci per implementare boost :: function yourself ... –

0

Presumo un'interfaccia come questa:

void Timer::register_callback(void(*callback)(void*user_data), void* user_data); 

template<typename AnyClass, (AnyClass::*Func_Value)(void)> 
void wrap_method_callback(void* class_pointer) 
{ 
    AnyClass*const self = reinterpret_cast<AnyClass*>(class_pointer); 
    (self->*Func_Value)(); 
} 

class A 
{ 
public: 
    void callback() 
    { std::cout << m_i << std::endl; } 
    int m_i; 
}; 

int main() 
{ 
    Timer t; 
    A a = { 10 }; 
    t.register_callback(&wrap_method_callback<A,&A::callback>, &a); 
} 

Penso che una soluzione migliore sarebbe quella di aggiornare il callback con la funzione boost :: function o una versione homegrown (come la risposta di Kornel). Tuttavia ciò richiede che siano coinvolti veri sviluppatori C++, altrimenti è molto probabile che introducano bug.

Il vantaggio della mia soluzione è che è solo una funzione di modello. Non tutto può andare storto. Uno svantaggio della mia soluzione è che può suddividere la tua classe con il cast a void* e viceversa. Fare attenzione che solo i puntatori AnyClass* passano come void* alla registrazione di richiamata.

Problemi correlati