2009-09-04 3 views
5

Ich habe eine benutzerdefinierte Menü-Klasse in C++ geschrieben. Um den Code in leicht lesbare Funktionen zu trennen, verwende ich Callbacks.Eine gute Möglichkeit, verwendbare Callbacks in C++ zu implementieren

Da ich Singletons für den Host des Menüs nicht verwenden möchte, stelle ich einen anderen Parameter (Ziel) zur Verfügung, der dem Callback als erster Parameter gegeben wird (eine Art Workaround für die fehlende "this" Referenz) .

Registration-Signatur

AddItem(string s, void(*callback)(void*,MenuItem*), void* target = NULL) 

Beispiel einer Registrierung

menu->AddItem(TRANSLATE, "translate", &MyApp::OnModeSelected); 

Beispiel für einen Handler

/* static */ 
void MyApp::OnModeSelected(void* that, MenuItem* item) { 
    MyApp *self = (MyApp*)that; 
    self->activeMode = item->text; 
} 

Gibt es alles, was man mit diesem Ansatz schmutzig könnte in Erwägung ziehen? Gibt es vielleicht bessere?

+0

Sie sollten Verwenden Sie hier keine statischen Member-Methoden als Callback, Sie sollten nur Funktionen verwenden, die extern "C" deklariert sind Sie haben zufällig Glück, dass der von Ihnen verwendete Compiler (zur Zeit) die gleiche Methode zum Aufruf von statischen Methoden verwendet und funktioniert. Das ist nicht durch den standard garantiert. –

Antwort

10

Ihr Ansatz erfordert, dass die Callback-Funktionen entweder freie Funktionen oder statische Member einer Klasse sind. Clients können Memberfunktionen nicht als Callbacks verwenden. Eine Lösung hierfür ist boost::function als Typ des Callback verwenden:

typedef boost::function<void (MenuItem*)> callback_type; 
AddItem(const std::string& s, const callback_type& callback = callback_type()); 

Clients dann boost::bind oder boost::lambda verwenden können, in der Callback weitergeben müssen:

menu->AddItem("Open", boost::bind(&MyClass::Open, this)); 

Eine weitere Option ist boost::signals zu verwenden, die erlaubt mehrere Callbacks für die Registrierung für das gleiche Ereignis.

+0

Ich würde verwenden 'MenuItem &' aber ansonsten völlig einverstanden. – MSalters

+0

Sie sollten keine statischen Member der Klasse als allgemeinen Callback verwenden (sie haben keinen definierten ABI). Die boost :: -Funktion ist nur eine Verallgemeinerung der Deklaration einer Schnittstelle (die boost :: -Funktion verwendet nur den Schnittstellenoperator()). Ich würde bevorzugen, dass meine Schnittstellen in diesem Fall etwas expliziter sind, so dass Sie keine unpassende Methode zurückgeben (dh den Compiler dazu bringen, den Callback zu validieren), wie von orsogufo beschrieben –

8

Ich mag Ihren Ansatz. Eine Alternative wäre eine Schnittstelle zu erklären, die in gewissem Sinne ist das „OO-Äquivalent“ eines Rückruf:

class IMenuEntry { 
public: 
    virtual void OnMenuEntrySelected(MenuItem* item) = 0; 
}; 

Die Registrierung Unterschrift

AddItem(string s, IMenuEntry * entry); 

Und die Methode Implementierung werden würde

class MyApp : public IMenuEntry { 
public: 
    virtual void OnMenuEntrySelected(MenuItem* item){ 
     activeMode = item->text; 
    } 
} 

Der Schnittstellenansatz ermöglicht es Ihnen, die "void * -Ausweichlösung" für den fehlenden Zeiger this zu vermeiden.

1

Ich sehe nichts falsch, außer dass die Funktionszeiger-Signatur schwer zu lesen ist. Aber ich würde wahrscheinlich observer Muster, um dies zu erreichen.

3

Sie könnten einen Blick auf boost::bind verwenden.

menu->AddItem(TRANSLATE, 
       "translate", 
       boost::bind(&MyApp::OnModeSelected, this, _1, _2)); 
1

Ich würde empfehlen, im boost::function und boost:bind dafür suchen. Wenn Sie es lernen, wird Ihre Funktion hundert Mal einfacher.

+0

auch, nicht vergessen boost :: lambda. Es kann manchmal lassen Sie weg ohne einen Rückruf insgesamt! – EFraim

+0

Boost :: Signale ist besser für diese Aufgabe. – Arpegius

1

Lesen this Weißbuch. Es baut verschiedene Techniken für einen Callback-Mechanismus auf, indem die Leistung, Benutzerfreundlichkeit und andere Kompromisse detailliert analysiert werden.Ich fand es eine harte Lese obwohl :-(

0

Ihr einen functor verwenden könnte Ihr Rückruf verkapseln. Auf diese Weise könnten Sie entweder eine C-Style-Funktion oder ein Objekt-Schnittstelle bereitzustellen, um den Rückruf zu verwenden.