Callback auf eine beliebige Klassenfunktion



  • Hat wer 'ne Idee für folgendes Problem.
    Ich möchte eine Callbackklasse haben, bei der man schon im voraus eine Instanz erstellen kann.
    Also wie hat die Klasse "MeineCallbacklasse" auszusehen damit so etwas möglich ist.

    class BeliebigeKlasse
    {
    	void BeliebigeFunktionMit0Parametern(void);
    	/*...*/
    }
    MeineCallbacklasse CallbackInstanz;
    BeliebigeKlasse BeliebigeKlasseInstanz;
    
    CallbackInstanz.CallbackinformationSpeichern(BeliebigeKlasseInstanz,&BeliebigeKlasse::BeliebigeFunktionMit0Parametern);
    CallbackInstanz.RufeCallbackauf();	//ruft BeliebigeKlasseInstanz.BeliebigeFunktionMit0Parametern() auf
    

    ähnliche Themen wurde ja schon mehrfach im Forum behandelt aber da ging man immer davon aus, das man alle Informationen über den Callback bereits genau kennt, wenn man die callbackklasse erzeugt.

    Ich möchte das Problem (wenn möglich) nicht durch Bibliotheken gelöst haben, die man sich noch irgendwo downloaden muß (wie boost)
    Danke im voraus.



  • sollte ohne weiteres nicht möglich sein, imho benutzt auch boost für sowas templates...



  • Geht nur über Templates, ist dann aber recht einfach.
    http://tutorial.schornboeck.net/meth_zeiger.htm



  • genau die will er ja nicht



  • Im Prinzip geht es, denn alles was man braucht ist der this-Zeiger und die Adresse der Methode. Hatte vor einer Weile mal einen Artikel auf CodeProject gelesen darüber. Allerdings geht das weit über die Grenzen von C++ hinaus.
    http://www.codeproject.com/cpp/FastDelegate.asp

    Meiner Meinung nach ein wichtiges fehlendes Sprachfeature von C++. Aufgrund der hässlichen Mehrfachvererbung aber auch schwierig zu realisieren.



  • wieso fehlendes Sprachmerkmal ?

    Ich glaub ich komm hier ned so recht auf das Problem ....

    Er will mittels callback ne memberfunktion aufrufen ... gut,
    dazu brauch er aber immer die instanz (fuer die daten) soweie die funktion (memberfunktion) ohne denen macht es gar keinen Sinn.

    Was ist nun sein eigentliches Problem ?

    Was kennt er beim deklarieren der Callbackklasse nicht ? Beim registrieren muss er ja alles kennen ....

    Kennt er den typ beim deklarieren, will die Callbackklasse aber generisch halten -> Templates.

    Kennt er den typ ned, hat er nur 2 moeglichkeiten ....

    1. er macht nen gemeinsammes merkmal zu nem Wirklichen gemeinsammen Merkmal (Aufrufbarkeit durch callback) -> Polymorphie / Vererbung ....

    2, oder er trennt um gottes willen das, was nimmer da zusammengehoert ... die daten von der funktion, macht die funktion eigenstaendig (kann er notfalls als Funktionspointer speichern notfalls, namespaces verwenden !) und uebergibt die eigentlichen daten als parameter ....

    Alles andere ist irgendwie ... aehm ... von hinten durch die Brust ins auge 🙂

    Glaub sein Problem ist eher das Design drumherum ....

    Ciao ...



  • 1. er macht nen gemeinsammes merkmal zu nem Wirklichen gemeinsammen Merkmal (Aufrufbarkeit durch callback) -> Polymorphie / Vererbung ....

    das will ich sehen wie er das machen soll, method pointer sind in der hinsicht etwas eigensinnig



  • Warum fehlendes Sprachmerkmal?

    In C++ wird der Funktions an den Objektzeiger gebunden - beide müssen zusammenpassen, sonst geht der Aufruf logischer Weise schief.

    Eine meiner Meinung nach nicht sinnvolle Beschränkung ist Meinung nach aber, dass der Typ des Objektzeigers (also die Klasse) bei Definition festliegen muss. Denn solange Objekt- und Funktionszeiger zusammenpassen, ist ja völlig egal, was da nun eigentlich aufgrufen wird.

    Lies dir mal den CodeProject Artikel durch, den ich gepostet habe, da wird das alles etwas klarer, warum das so ist in C++.

    In anderen Sprachen ist das wesentlich besser gelöst.



  • Eine meiner Meinung nach nicht sinnvolle Beschränkung ist Meinung nach aber, dass der Typ des Objektzeigers (also die Klasse) bei Definition festliegen muss. Denn solange Objekt- und Funktionszeiger zusammenpassen, ist ja völlig egal, was da nun eigentlich aufgrufen wird.

    komischerweise kann man aber einen objektzeiger auf eine klasse haben die noch garnicht definiert ist. Und laufzeitdynamik mit methodpointern funktioniert auch nicht, da man sie schlichtweg nicht casten kann.



  • Wieso nicht einfach eine abstrakte Callback Klasse verwenden von der abgeleitet wird?



  • Na toll bin ich einen halben Tag nicht da und es entbrennt sofort eine Diskussion wie ich das gemeint hab 😞
    Also ich kenne die Instanz der Klasse und die Funtion der Klasse, die ich aufrufen will.
    Jedoch muß die Instanz der Callbackklasse schon vorher erstellt werden.

    OK und um das Problem mal anders zu beschreiben.
    Ich und ihr habt sicher schon oft tutorials gesehen, wo die einen Funktionspointerarray benutzen. und je nachdem welches Arrayelement verwendet wird, wird dann z.B. die Funktion "int Plus (int a, int b)" oder "int Minus (int a, int b)" aufgerufen.
    So und ich brauche auch so etwas; nur das es sich um einen Klassenfunktionspointerarray handelt. Und den "Klassenfunktionspointer" wollte ich in der Callbackklasse speichern. Ich brauche also eine Callbackklasse mit der man dann einen entsprechenden Callbackklassenarray machen kann.

    Hat wer hier ne Idee, wie man so eine Callbackklasse aufbauen sollte, denn ich glaub Templates bringen hier also eher gar nichts.



  • Wenn ich Dich richtig versteh:

    Du willst in Deiner Callback-Klassen-Instanz beliebige Klassenfunktionen (mit void als Parameter und Rückgabewert) speichern, ohne dass Du bei der Instanziierung der Callback-Instanz weißt, welche Klasse das ist, von der Du die CallBack-Methode aufrufen willst.

    ok?
    Nicht compiliert und nicht getestet:

    class Caller //Basisklass für die Classe, die den Call durchführt
    {
    public:
      //abgeleitete Klasse implementiert aufruf der Klassenfunktion
      virtual void call()=0;
    };
    
    //Für jede Klasse, die Aufgerufen werden soll zu definieren
     template<typename T>
    class SpecificCaller
    :public Caller
    {
      typedef void (T::*function2Call_type)();
    private:
      function2Call_type function2Call_;
      T& instance2Call_;
    public:
      SpecificCaller( T& instance2Call,
                      function2Call_type function2Call)
      :function2Call_function2Call,
       instance2Call_( instance2Call)
      {
      }
    
      virtual void call()
      {
        instance2Call.function2Call_();
      }
    };
    
    //Callback-Klasse. Ruft in Zusammenarbeit mit Caller
    //jede beliebige Klassenfunktion der Signatur
    //void(class::*function)() auf
    class Callback
    {
      Caller* pCaller_;           //Zeiger auf Caller
    public:
      void setCaller( Caller& Caller)
      {
        pCaller = &Caller;
      }
    
      void execCallBack()
      {
        pCaller->call();
      }
    };
    
    /////////////////////////////
    //Anwendung
    /////////////////////////////
    
    class Bar
    {
    public:
      void callMe()
      {
        cout << "aufgerufen";
      }
    };
    
    void foo()
    {
      CallBack callBack;  //Aufzurufende Instanz noch nicht bekannt
    
      Bar bar; //beliebige Klasse instanzieren
    
      SpecificCaller< Bar> barCaller( bar, Bar::callMe); // SpecificCaller für
                                                         // beliebige Klasse definieren
    
      callBack.setCaller( & barCaller); //SpecificCaller als Zeiger
                                        //auf Basisklasse 'Caller' übergeben
    
      callBack.execCallBack(); //Callback durchführen
    }
    


  • @Janko
    Das was du beschreibst, ist schon nen Versuch einer Loesung eines anderen Problems, und nicht das eigentliche Problem 😃

    - Funktionspointer-Arrays sind was fuer Hobby-Masochisten ... aus der Sicht von C++ Programmierern. In C++ gibts normal bessere wege. Sicher kommen jetzt wieder beispiele wo aus performance gruenden Funktionspointer Arrays in diversen mehr oder weniger schmutzigen Libs verwendet werden, aber auch da gibts meist elegantere wege ....

    - Auf dein problem stossen mit sicherheit auch einige andere auch, nur werdens sicher auf anderen weg loesen, und ratschlaege koennen wir da ned so geben, weil wir zu wenig ums drumherum wissen. Glaub mal, du generalisierst dein eh schon abstraktes beispiel noch mal.

    Ok, aber zu deinem abstrakten Problem genau ....

    deine Callbackklasse Call erstellt eine Instanz mCall, diese soll von einem Member mClient der Klasse Client eine Funktion aufrufen ....
    die funktion hat ne bestimmte Signatur void f() z.b.

    A. Frage, soll die funktion immer Gleich sein, oder soll man unterschiedliche funktionen (methoden) aufrufen koennen ???

    B. Informationen die du hasst ....
    deine CallbackKlasse hat 4 Punkte in ihrer Existenz.... weche informationen hasst du an welchem Punkt ?

    1. Punkt, du definierst(programmierst, compiletime) deine Klasse Call, welche infos hasst du da ?
    - die Signatur der Aufzurufenden Funktion von deinem Client Member
    - den Namen der Klasse, also das Symbol, des Client Members
    - den Namen, also das Symbol, der aufzurufenden Funktion

    2. du verwendest die Klasse Call an ner Stelle und codest das Erzeugen der Instanz mCall der Klasse Call(compiletime), welche Informationen hasst zu dem Zeitpunkt ?
    - die Signatur der Aufzurufenden Funktion von deinem Client Member
    - den Namen der Klasse, also das Symbol, des Client Members
    - den Namen, also das Symbol, der aufzurufenden Funktion

    3. du erzeugst die Instanz mCall der Klasse Call(Runtime), welche Information hasst du da ?
    - die Signatur der Aufzurufenden Funktion von deinem Client Member
    - den Namen der Klasse, also das Symbol, des Client Members
    - den Namen, also das Symbol, der aufzurufenden Funktion
    - die Daten, also ne schon erstellte Instanz mClient der Klasse Client

    4. Du registrierst den Callaufruf, welche Informationen hasst du ?
    - die Signatur der Aufzurufenden Funktion von deinem Client Member
    - den Namen der Klasse, also das Symbol, des Client Members
    - den Namen, also das Symbol, der aufzurufenden Funktion
    - die Daten, also ne schon erstellte Instanz mClient der Klasse Client

    Denk mal das spaetestens bei punkt 4 alle gefragten Informationen haben solltest .... sonst hasst nen logischens problem ....
    Hauptsaechlichst punkt 1 und 2 sind wichtig ....

    Anhand dieses Musters kannst dir dann ne Loesung besser ausdenken ... oder besser noch mal drueber nachdenken, ob die Informationen wirklich so spaet kommen, und ob man die ned nach vorn verlegen sollt.
    Ne Loesung wo alle informationen an punt 4 kommen ... wird man wohl mit weitreichenden Designaenderungen umgehen muessen .....

    Ciao ...



  • @Kartoffelsack
    danke für die Mühe, hilft mir aber leider nicht.
    Ich muß die Klasse SpecificCaller< Bar> barCaller( bar, Bar::callMe)
    ja irgendwie bis zum Callbackaufruf wegspeichern können und das geht bei templates nicht.

    @RHBaum
    zu Frage A:
    Es ist immer void f(void)
    zu Frage B:
    Also bei 4. hätt ich alle Infos. Leider nicht früher.

    Hm na gut werd das ganze dann mal aufgeben. Es scheint da wohl keine Lösung zu geben. Werd' euch statt dessen mal erzählen was ich eigentlich machen wollt.
    -> Soll gleichzeitig folgendes wiederlegen

    Funktionspointer-Arrays sind was fuer Hobby-Masochisten ... aus der Sicht von C++ Programmierern

    Früher hatte ich z.B. geschrieben
    Update
    {
    (Pseudocode)
    Wenn P seit dem letzten aufruf gedrückt wurde dann toggle die Variable bool randompos;
    Wenn C seit dem letzten aufruf gedrückt wurde dann rufe die Funktion ZeichneKreis() auf;
    Wenn E seit dem letzten aufruf gedrückt wurde dann rufe Screen.empty() auf;
    /.../
    }
    So und eine Zeile Pseudocode entsprach da dann immer 2-4 Zeilen realen code. Deshalb habe ich ne Klasse EInputAnalyser geschrieben.
    Ich schreib dann einfach in die Initialisierungsfunktion
    EInputAnalyser::AddVerknuepfung('P',en_ToggleBool,(void*)&random_pos);
    EInputAnalyser::AddVerknuepfung('C',en_CallFunc ,&ZeichneKreis);
    /.../
    und in die Funktion Update
    EInputAnalyser::Update();
    Vorteil von meiner Klasse (wenn sie dann fertig ist) ist, dass man dann immer bestimmte Eingaben-/Eingabengruppen-/alle EingabenVerarbeiungen deaktiviern, aktivieren, hinzufügen, löschen etc kann.
    Und der Nachteil ist dass man in ihr keine Klassenfunktionspointer speichern kann 😞
    Naja ich hab auch keine Idee wie ich meinen Code so kurz und übersichtlich behalte wie jetzt und nebenbei Klassenfunktionspointer verwenden kann.

    Ich könnte es maximal mit der "altmodischen" Art versuchen. Also statt in der Funktion Initialisierung mehrmals die Klassenfuntkion AddVerknuepfung aufzurufen, einfach in der Funktion update entsprechende Funktionen VerarbeiteVerknüpung() aufrufen.
    Jedoch gehen mir dann die Möglichkeiten verloren schnell und einfach Eingabeverarbeitungen zu deaktivieren hinzuzufügen etc. eben weil ich dann eine Eingabeverarbeitung nicht mehr als Objekte handhaben kann.



  • Ich muß die Klasse SpecificCaller< Bar> barCaller( bar, Bar::callMe)
    ja irgendwie bis zum Callbackaufruf wegspeichern können und das geht bei templates nicht.

    klaro geht das (deswegen ist ja SpecificCaller<> von Caller abgeleitet - oh, da fällt mir auf, das hatt ich ja garnicht drinnen im Code 😉 )

    std::vector< Caller*> callers;  //container mit den Aufrufern
    Caller* pcaller = new SpecificCaller<Bar>(bar, bar::callMe);
    
    callers.push_back( pcaller);
    

    Wenn Du 'bar' auch noch speichern musst, kannst Du SpecificCaller so umschreiben, dass es einen Zeiger bekommt (und diesen im Destruktor freigibts).
    Eleganter ist es natürlich mit std::auto_ptr

    template <typename T>
    class SpecificCaller
    :public Caller
    {
      typedef void (T::*function2Call_type)();
    private:
      function2Call_type function2Call_;
      std::auto_ptr<T> instance2Call_;
    public:
      SpecificCaller( std::auto_ptr<T> instance2Call,
                      function2Call_type function2Call)
      :function2Call_function2Call,
       instance2Call_( instance2Call)
      {
      }
    
      virtual void call()
      {
        instance2Call->function2Call_();
      }
    };
    
    std::vector< Caller*> callers;  //container mit den Aufrufern
    
    std::auto_ptr< Bar> bar = new Bar;
    
    Caller* pcaller = new SpecificCaller<Bar>(bar, Bar::callMe);
    
    callers.push_back( pcaller);
    

    Jetzt ist das Leben von bar an das Leben von caller gebunden.
    Am allerelegantesten ist es natürlich mit boost::smart_ptr. Weil den kannste dann auch in den Vector Reintun und Du musst dich nirgends mehr explizit ums freigeben kümmern.



  • @kartoffel:
    std::auto_ptr kann man nicht im std::vector speichern. boost::shared_ptr wäre richig.



  • Ok, dann auf das praktische bezogen ...

    EInputAnalyser::AddVerknuepfung('P',en_ToggleBool,(void*)&random_pos);
    

    Ok, da du zum ausfuehren genau 2 sachen brauchst, nämlich die funktion (die methode genauer) und die daten (die instanz die die daten enthaelt), nehm ich mal an, dass en_ToggleBool deine daten (struktur etc) sind, und random_pos deine Funktion(in dem Fall noch unabhaengige) zu ?

    Was hindert dich daran, die ganzen en_xxxx instanzen von einer gemeinsamen basis abzuleiten ? Nun Musst nur noch fuer die versplittung der Befehle sorgen ....

    deine EInputAnalyser Klasse ist die einzige die die Informationen zu hat ....

    Du solltest die Info fuer die Richtige funktion / Methode in irgend nen typ unterbringen.

    1. Ansatz waere nen enum ,sowes wie:
    EInputAnalyser::AddVerknuepfung('P',ToggleBool,cmd_randompos)

    wobei ToggleBool ne abgeleitete Klasse von nem CommandInterface ist, und diese ne ExecuteMethode mit dem Enum als Parameter erbt und definiert.
    cmd_randompos ist dann dein enum, der angibt was genau der befehl ist den dein Togglebool ausfuehren soll ....

    Nachteil, du muesstest nen enum mit ner menge definitionen deklarieren ....

    2. Ansatz
    Eine Abstrakte COmmand Klasse ... mit ner Funktion void Execute();

    Dazu muesste deine Obejekte, die die modifiziert werden sollen, gleichzeitig Objectfabriken fuer die Command objecte sein .... und diese auch Pflegen ....

    saehe dann ca. so aus:
    ICommand * MyViewClass::Create_ZeichneKreisCmd();
    damit kannst dir an ner Initalen stelle die CommandObjecte erzeugen lassen .... die commandobjecte halten intern irgendwie nen verweis auf die zu modifizierenden objecte, und die zu modifizierenden Objecte verwalten die gleichzeitig ....

    ICommand pCmd = mview.ZeichneKreisCmd();

    diese commandobject kannst nun registrieren ....
    EInputAnalyser::AddVerknuepfung(char cKey,ICommand * pcommand);

    und wenn dann der benutzer die taste drueckt, und der zugeordnete commandpointer ist ned null, dann ruft EInputAnalyser einfach pcommand->Exceute() auf ... und das geroedel beginnt ....

    Was gefaellt dir an soclchen loesungen nicht ?

    Ciao ...



  • std::auto_ptr kann man nicht im std::vector speichern. boost::shared_ptr wäre richig.

    Hab ich das getan? Hab ich das nicht geschrieben 😕 😕

    Ok, dann auf das praktische bezogen ...

    was soll das heißen 🤡

    Was hindert dich daran, die ganzen en_xxxx instanzen von einer gemeinsamen basis abzuleiten ?

    Vielleicht gibts die en_xxx-Klassen schon und man kann sie nicht einfach umschreiben?
    Was geht es überhaupt die en_xxxx-Klassen an, dass sie von irgendeinem schlauen Callback-Mechanismus aufgerufen werden. Das wiederspricht der Kapselung, der Reduktion von Abhängigkeiten, gutem Design und k.a. was.

    Das Zauberwort ist 'another level of indirection'.

    Man hat eine Klasse A, man hat eine Klasse B.
    A soll B aufrufen, aber A weiß nicht, wie man B aufruft, weil A nur eine Art des Aufrufens kennt. B weiß von A garnix.
    Was ist zu tun. Man verpackt B in der Form, dass es sich so aufrufen lässt, wie A das möchte.

    In ner klassischen objektorientierten Programmiersprache sähe das so aus:

    - Basisklasse (oder Interface) X die die Schnittstelle bietet, die A braucht
    - Für jede aufzurufende Klasse eine Verpackungsklasse Y1, Y3 ..., die die Basisklasse implementiert (und damit deren Typ hat und deren A bekannte Schnittstelle bietet) und den Aufruf intern in einen Aufruf für B umsetzt.

    Y1, Y2, Y3 sehen jetzt alle ziehmlich ähnlich aus, wenn die B-artigen Klassen einen ähnlichen Aufrufmechanismus haben (keine Rückgabe, kein Parameter).
    -> in C++ haben wir Templates und müssen somit nur eine universelle Y-Klasse schreiben
    --> voila: Wir brauchen B nicht ändern und A braucht von B trotzdem nix zu wissen. Und das ist supa, weil wir keine unnötigen Abhängigkeiten geschaffen haben.



  • Was hindert dich daran, die ganzen en_xxxx instanzen von einer gemeinsamen basis abzuleiten ?

    Das wiederspricht der Kapselung, der Reduktion von Abhängigkeiten, gutem Design und k.a. was.

    Klar, sollt man ein gesundes Mass an indeskretion einfuehren, um die Abhaengigkeiten aufzuloesen .... aber lass ihn erst mal von den funktionspointern wegkommen und begreifen, dass er die normal nie braucht ...

    So wie ich ihn verstanden hab, hat er das design auch der client klassen schon in der hand ... wenn nicht, waere das die 2.Frage und der 2. Schritt in die richtige Richtung gewesen ....

    @Janko
    Tipp: Kauf dir nen gescheites Buch uber DesignPatters, wo ned nur die Patterns gelistet sind sondern auch bisserl drumherum beschrieben wird. Dann bekommst schon paar Lektionen in sachen Sauberes Design, aufloesen von abhaengigkeiten ... etc ...

    Ciao ...



  • Klar, sollt man ein gesundes Mass an indeskretion einfuehren

    dafür bin ich auf alle Fälle zu haben 😃 😉 🙂


Log in to reply