Functorprobleme
-
Hi,
ich bin gerade dabei, ein Functortemplate bei mir zu implementieren. Dazu will ich zwei Templateklassen verwenden, nämlich Event und Callback. Ein Event ist dabei eine Templateklasse, die einen vector von Callbacks enthält, die alle beim Auslösen dieses Events aufgerufen werden. Ein Callback ist quasi ein klassischer Functor, kann also wie man will auf alle beliebigen Funktionen zeigen (vielleicht sogar virtuelle, aber da bin ich mir nicht sicher
).Jetzt gibt es "nur" drei Probleme:
1. Für verschiedene Anzahlen von Parametern müsste ich ja viele verschiedene Event- und Notifierklassen entwerfen, also z.B. Notifier1<result>, Notifier2<result, arg1>, Notifier<result, arg1, arg2> usw.
Gibt es irgendeine Möglichkeit, das zu umgehen und trotzdem die C++ Typsicherheit zu erhalten? Oder kann ich die vielen Klassen (muss ja schon bis 20 oder so gehen) per Makro erzeugen lassen, da der Präprozessor keine Schleifen usw. kennt, dürfte es aber auch dann schwer werden, die ganzen Parameter in die Ausgabedatei des PP schreiben zu lassen...
2. Muss Event überhaupt ein Template sein oder könnte ich auch einfach eine Hauptklasse für alle Functors machen und die in den Vector in Event reinspeichern? Dann hätte ich aber auch keine Typsicherheit mehr, die sicherstellt, dass nur passende Notifier für das Event registriert werden, oder?

3. Wie könnte ich sowas wie bei libsigc++ durch sigc::bind erreichen, also dass man eine Funktion mit z.B. 3 int Parametern als ein Functor mit 2 int Parametern speichert, aber den letzten int Parameter fest vorgibt (z.B. um bei Callback immer eine bestimmte ID zu haben)?
Ich hab in libsigc++ nachgeschaut, aber ich komm nicht dahinter, wie das dort realisiert wird
ChrisM
[ Dieser Beitrag wurde am 21.06.2003 um 16:37 Uhr von ChrisM editiert. ]
-
Hallo,
kann nur etwas zu 1 sagen und zwar glaube ich nicht, daß Templates eine variable Anzahl an Parametern dulden. Ich hab eine kl. Menge an Functor-Klassen mal so implementiert:
Functor<T> <- T: Typ des Objekts, auf dem ein Methodenaufruf erfolgen soll
Functor1P<T, P1> <- T: s.o. P1: Typ Parameter1 der Methode
... bis P4
FunctorR<T, R> <- T: s.o. R: Typ Return-Wert der MethodeFunctor1PR<T, P1, R>... Functor4PR<T, P1, P2, P3, P4, R>
war ziemlich viel Schreibkram und ist auch recht unelegant.
-
Sowas hab ich schon befürchtet, das heißt dann für mich 20 Klassen für maximal 20 Argumente, wenn Rückgabewert immer dabei ist

Und deine Konstruktoren haben dann immer 2 Parameter, d.h. man muss die Memberfunktion und die Klasse (this beim Aufruf) angeben? Was macht man dann, wenn man nur eine ganz normale Funktion als Callback einsetzen will? Reicht es, wenn man dann für die Klasse 0 übergibt?
ChrisM
-
klar vererbung. die notifier rufen immer methoden auf (auch virtuelle, wenn du magst) und beinhalten zeiger auf zielobjekt, zeiger auf methode und bis zu 3 parameter.
also Notifier0 bis Notifier4.
äh,
template<class O,class P0,class P1,class P2,class P3>
class Notifier4:public NotifierBase
{
...
virtual void shoot()
{
target->func(p0,p1,p2,p3);//oder so
}
};
und Zeiger auf NotifierBase gammeln im vector rum, den das event hat.
-
Ne, das geht bei mir leider nicht, weil die Template-Klasse ja als Template-Parameter den Typ der Klasse verlangt; so kann man leider keine normale C-Funktion ausdrücken

-
Original erstellt von ChrisM:
**Was macht man dann, wenn man nur eine ganz normale Funktion als Callback einsetzen will? Reicht es, wenn man dann für die Klasse 0 übergibt?
**dann frag ich mich erstmal nach dem desingfehler.
kannst zur nor noch ne notifier-klasse bauen, die globale funtionen isst.
die clients sehen von den notifier-klassen ja eh nix, sondern rufen ja bei den wächtern methoden auf um sich zu registrieren.
-
kannst zur nor noch ne notifier-klasse bauen, die globale funtionen isst.
Das verdoppelt nochmal die Anzahl der Notifierklassen.

die clients sehen von den notifier-klassen ja eh nix, sondern rufen ja bei den wächtern methoden auf um sich zu registrieren.
Wie meinst du das? Ich denk, die Notifiertemplates werden direkt vom User instantiiert?
ChrisM
-
Original erstellt von ChrisM:
Wie meinst du das? Ich denk, die Notifiertemplates werden direkt vom User instantiiert?ich mein, daß die nur aufrufen blaevent.addWatcher(this,&Fasel::onMouse); und addWatcher ist ne template-methode, die schon alles richtug regeln wird.
-
Achso, du meinst, AddWatcher() wird dann für jeden Notifier einmal überladen, also z.B.
template<class Klasse, class Return> AddWatcher(Klasse, (Return)*(void)); template<class Klasse, class Return, class Param1, class Param2> AddWatcher(Klasse, (Return)*(Param1, Param));Seh ich das so richtig?
ChrisM
-
Original erstellt von ChrisM:
**template<class Klasse, class Return, class Param1, class Param2>
AddWatcher(Klasse, (Return)*(Param1, Param));[/code]
Seh ich das so richtig?
**ja, aber eher noch
template<class Klasse, class Return, class Param1, class Param2> AddWatcher(Klasse, (Return)*(Param1, Param),Param1,Param2);ob das das beste für dich ist, weiß ich nicht, aber ich hab sowas mal gemacht.
und die 20 parameter kannste dir abscmingen. bau mal drei, und vermutlich reicht das. bau 4 erst, wenn du auch wirklich mal 4 parameter brauchst.
-
Danke erstmal, dass ihr überhaupt was postet

Warum gibst du bei AddWatcher nochmal Werte für die Parameter an? Der Zeiger und this für Memberfunktionen müssten doch reichen, oder?
Wenn mir dann noch jemand bei Frage 2 und 3 helfen könnte...

ChrisM
-
Original erstellt von ChrisM:
Warum gibst du bei AddWatcher nochmal Werte für die Parameter an? Der Zeiger und this für Memberfunktionen müssten doch reichen, oder?
ChrisMtja, ich dachte, du wolltest sagen können "wenn da ne mousemessage anliegt, dann ruf bitte "this->onMouse("imagemap1",23)" auf.
-
Nein, das ist ja meine Frage 3

Ich hab mir das so vorgestellt:
Meine Mausklasse (um bei deinem Beispiel zu bleiben) hat ein Event als Member für das Klick-Ereignis. Nun speichert dieser Event normalerweise Funktionen mit 3 Ints (Koordinaten + Maustaste). Jetzt kommt aber ein Benutzer und initialisiert die Mausklasse 2 mal, er will aber nur eine Callbackfunktion benutzen. Also gibt er der Callbackfunktion als 4. Parameter noch einen int. Beim Erstellen der Notifiers wird dieser 4. Parameter vom Benutzer auf die ID der Maus festgelegt, also bei der einen Mausinstanz auf 1, bei der anderen auf 2. Trotzdem kann dieser Notifier im Event gespeichert werden, da er als Notifier mit 3 Parametern und nicht als einer mit 4 zählt!Geht das irgendwie? (in libsigc++ gehts wie gesagt mit sig::bind)
ChrisM
-
*push*
ChrisM
-
Bei solcher Callback-Problematik gibt meist eine Totschlag-Technik:
Man definiere eine Klasse...
1. Als Interface, d.h. Sie besitzt eine rein virtuelle Funktion z.B. "Execute()"2. Wird die Klasse als Klassen-Template angelegt, kann der Rückgabewert vom Typ <typename ...> sein
3. Alle Parameter, die die Callback-Funktion ausführen soll, werden als Member gespeichert. Das erfordert allerdings entweder eine Ableitung, oder es wird eine Datenstruktur gespeichert (als void-Zeiger, oder durch Template-Technik während der Instantiierung).
Wenn das Event aufgerufen wird, muß diesem nur ein Parameter "TCallBack* CallMe" übergeben werden, egal welche Ableitung davon nun *wirklich* übergeben wurde.
class TCallback{ void* Returned; void* Data; vitural void Execute(void)=0; // rein virtuell - das zwingt zum Überschreiben. }; // Alternativ: template <typename D, typename R> class TCallBack{ R* Returned; D* Data; virtual void Execute(void) =0; }; // Implementierungs-Beispiel: class TMausCallBack : public TCallBack{ // Der Konstruktor *kann* Parameter vorbelegen TMausCallBack(int& Param1, std:String& Param2, SomeClass* Param2){ _param1=Param1; _param2=Param2; if(Param3)_param3=Param3; else _param3 = new SomeClass(); // Veranschalichte Plausibilitätsprüfung } virtual void Execute(){ //Hier finden unglaublich komplexe Berechnungen statt ... Returning = 42; } int _param1; protected: std::String _param2; SomeClass* _param3; // Es muß nicht der gesamte Algorythmus in Execute liegen // Es kann eine Lambdafunktion oder statische Funktion sein... int Helfer(std::String& str); }; //Anwendung: ... int THandler::DoHandle(int x, TCallBack* CallMe){ if(CallMe){ CallMe->Data+=x; //Weise irgentwelche Parameter zu, erzeuge Daten... CallMe->Execute(); return &((int*)CallMe->Returned); } return 0; }Dieser Ansatz erfordert natürlich, daß im ausführenden Code eine bestimmte Ableitung erwartet wird ... ist also nicht wirklich generisch - sondern nur das, was man vor 20 Jahren darunter verstanden hätte.
![;] 😉](https://www.c-plusplus.net/forum/plugins/nodebb-plugin-emoji/emoji/emoji-one/1f609.png?v=ab1pehoraso)
Ich weiß, das ist eher eine Fleischhammer-Technik, aber Du willst es Dir ja auch nicht zu leicht machen!Außerdem könnte Dir da Boost vielleicht weiterhelfen:
Schau' Dir doch mal Boost::function und Boost::Signals an, vielleicht könntest Du daraus ja etwas machen...
http://www.highscore.de/cpp/boost/ereignisbehandlung.html
-
Gleich im Voraus: BinärWichtel ist ein Semi-Troll, dieser Thread ist von 2003. Wird (wahrscheinlich) gleich gesperrt.