2012-08-13 12 views
5

In un progetto non boost, ho una classe che utilizza un timer basato su una determinata azione dell'utente (pulsante premuto/rilasciato). Voglio questa classe generica, quindi accetta le callback per le azioni definite dall'utente.come passare i callback C++ tra classi non correlate?

// TimerClass.h 
typedef void (*timerCallback)(void); 
... 
Class TimerClass : public CButton { 
public: 
    void setCallbackShortTime(timerCallback clickFn) { shortCallback = clickFn;} ; 
    void setCallbackLongTime(timerCallback clickFn) { longCallback = clickFn;} ; 
private: 
    timerCallback shortCallback, longCallback; 
} 


// CMyDlg.h 
class CMyDlg : public CDialog 
{ 
public: 
    void openLiveViewWindow(); 
    void openAdminPanelWindow(); 
    TimerClass _buttonSettings; 
} 

// CMyDlg.cpp 
... 
_buttonSettings.setCallbackShortTime(&openLiveViewWindow); 
... 

Ora, da un'altra classe (DialogClass) posso usare il TimerClass ma non posso passare puntatori a funzione per le funzioni di callback. Queste funzioni non sono statiche. Il compilatore finisce per lamentarsi:

error C2276: '&' : illegal operation on bound member function expression 

qualche ricerca su questo rilevare a std::function() e std::bind() ma io non sono familiarità con questi e apprezzerebbe alcune indicazioni su come risolvere questo.

RISOLUZIONE: Per chiunque sia interessato, qui ci sono i mattoni della soluzione finale

// TimedButton.h 
#include <functional> 
// define timer callback prototype 
typedef std::function<void()> timerCallback; 
... 
class TimedButton : public CButton 
{ 
public: 
    TimedButton(); 
    ... 
    void setCallbackShortTime(timerCallback clickFn) { _shortCallback = clickFn;} ; 
    void setCallbackLongTime(timerCallback clickFn) { _longCallback = clickFn;} ; 
private: 
    timerCallback _shortCallback; 
    timerCallback _longCallback; 
} 

// TimedButton.cpp 
... 
(_longCallback)(); // call long duration fn 
... 
(_shortCallback)();  // call short duration fn 

// in MyDlg.cpp 
#include <functional> 
... 
_buttonSettings.setCallbackShortTime(std::bind(&CMyDlg::openLiveViewWindow, this)); 
_buttonSettings.setCallbackLongTime(std::bind(&CMyDlg::openAdminPanelWindow, this)); 
+0

i callback per le funzioni membro si comportano diversamente a causa del parametro implicito 'this'. –

+0

Senza rendere statici i metodi, è necessario mantenere un riferimento all'istanza all'interno del timer. Stai usando C++ 11? – jli

+0

non stiamo usando C++ 11, ma quello precedente come abbiamo il set di funzioni 'tr1' – fduff

risposta

2

std::function è un oggetto funzione polimorfica, in grado di avvolgere qualsiasi tipo di oggetto richiamabile con una firma particolare. Nel tuo caso, si vuole prendere senza argomenti e restituire alcun valore, in modo da poter definire:

typedef std::function<void()> timerCallback; 

std::bind, permette di adattare un oggetto callable ad uno di una firma diversa, con argomenti vincolante ai parametri. Nel tuo caso, si vuole adattare una funzione di membro legandosi ad un particolare oggetto per invocare su:

_buttonSettings.setCallbackShortTime(std::bind(&CMyDlg::openLiveViewWindow, this)); 

Nota che queste sono state introdotte nel 2011, in modo da compilatori più vecchi non sostenerli. In tal caso, potresti utilizzare le librerie Boost o TR1 molto simili oppure creare la tua classe callable contenente una funzione pointer-to-member e un puntatore/riferimento all'oggetto su cui desideri richiamarlo.

+0

saluta Mike per avermi indicato la direzione giusta. – fduff

2

Non è possibile passare un puntatore a un metodo di una classe, solo funzioni semplici. Suggerisco di scavare in std::function(), dal momento che stai usando VS2010, che li supporta. C'è un bel (e lungo) tutorial che li descrive e altri here.

+1

Un piccolo chiarimento: il problema qui è che 'timerCallback' è definito come un puntatore a una funzione ordinaria, quindi non è possibile memorizzare un puntatore alla funzione membro in esso. Se fosse stato definito come un puntatore alla funzione membro, allora potreste. Ma poi dovresti avere un puntatore "this" da qualche parte per chiamarlo. Ecco perché std :: bind è la risposta giusta, come altri hanno già detto. –

+0

Sì, grazie per il chiarimento. Avrei dovuto fornire tale in primo luogo ... :) –

+0

grazie per il collegamento, davvero molto interessante. – fduff

3

Il metodo obsoleto per fare ciò è di rendere la funzione di richiamata accetta un ulteriore parametro void*, per il quale si passa un puntatore all'oggetto su cui si desidera richiamare la funzione. Quindi si utilizza una funzione membro statico per la richiamata e si consente di trasmettere il puntatore all'oggetto appropriato e chiamare il proprio callback.

typedef void (*timerCallback)(void*); 
... 
void setCallbackShortTime(timerCallback clickFn, void* object) { shortCallback = clickFn; shortCallbackObject = object;} ; 
void setCallbackLongTime(timerCallback clickFn, void* object) { longCallback = clickFn; longCallbackObject = object;} ; 
... 

static void openLiveViewWindowCallback(void* object) { ((CMyDlg*)object)->openLiveViewWindow(); } 
void openLiveViewWindow(); 
+0

È vecchio stile? L'ho usato spesso! – acraig5075

+0

è un modo molto simile a C di farlo. L'unico problema è che il callback deve conoscere il tipo oggetto '((CMyDlg *) oggetto) ->' che è contro lo scopo di una classe generica. – fduff

1

È possibile creare una classe modello polimorfica che funge da puntatore a funzione.

class TFunctorBase 
{ 
public: 
    TFunctorBase(void) {} 
    virtual ~TFunctorBase(void) {} 
    virtual void operator()(void) = 0; 
}; 

// derived template class 
template <class TClass> class TFunctor : public TFunctorBase 
{ 
private: 
    void (TClass::*fpt)(); // pointer to member function 
    TClass* pt2Object;     // pointer to object 

public: 
    TFunctor(TClass* _pt2Object, void(TClass::*_fpt)()) 
    { pt2Object = _pt2Object; fpt = _fpt;}; 

    virtual void operator()(void) 
    { (*pt2Object.*fpt)();};    // execute member function 
}; 

Per inizializzare un oggetto funtore:

TFunctor<MyClass> obj(&myInstance, &MyClass::myMemberFunction); 

E per usarlo:

(*obj)(); 
//(*obj)(42); for operator()(int) 

Ecco un esempio:

class ClassA 
{ 
public: 
    void function1(int a, string b); 
}; 

ClassA objA; 
TFunctor<ClassA> functor(&objA,&ClassA::function); 
(*functor)(42, "pumpkin"); //assuming you added virtual void operator()(int, string) to TFunctorBase 

Ecco una grande risorsa su funtori lungo con l'implementazione che ho descritto sopra. http://www.newty.de/fpt/functor.html#chapter4

+0

Oppure potresti usare 'std :: bind', che ha diversi vantaggi: è molto più potente, è nella libreria standard (a partire da C++ 11) quindi più riconoscibile per i futuri manutentori del tuo codice e più portabile, e qualcuno altro è responsabile per il suo mantenimento. –

Problemi correlati