Anordnung von memberfunktionen im Speicher



  • Nach einigen Versuchen mit Delegates, wollte ich jetzt mal was schickes basteln um sie anzuwenden.
    Parktischer Hintergrund:

    in einer beliebigen Klasse sind verschiedene Memberfunktionen

    class AClass
    {
       public:
              void OnDoSomething();
              void OnDoOnotherthing();
              ....usw...
              void OnDoLastthing();
    };
    

    Die will ich jetzt komfortabel an einen Dialog übergeben, um sie mittels Delegates an Hotkeys zu binden. An sich kein problem, ich bin auf mehrere mehr oder weniger schöne varianten gestoßen:

    1.) Ich binde jede Funktion im Quelltext manuell
    2.) Ich parse das cpp-file und generiere mir daraus ein eigenes cpp, das die
    eigentliche Funktionsliste enthält und bindet
    3.) Ich finde eine Möglichkeit durch die Memberfunktionen einer beliebigen
    Klasse zu iterieren und nach Funktionssignatur auszuwählen.

    1.) halte ich für komplett ungeeignent, da jede änderng dann nachgezogen werden
    muß. 2.) hat den Vorteil, da ich bei oben genannten Beispiel problemlos nach allen Funktionen suchen kann, die die signatur void On..() haben. Für 3.) habe ich derzeit nur bei der MFC eine Idee, weil ich dort nur durch die MessageMap iterieren muß. Ich hätte aber schon gerne eine MFC-unabhängige Lösung.

    Also ist meine derzeitiger bevorzugter Ansatz wohl 2.) (muß ja auch einen Grund geben, warum bei QT die häßlichen MOCs erzeugt werden:))

    meine eigentliche Frage ist jetzt zu 3.), um mal ein bisschen besser zu verstehen, was intern so abläuft.

    Ich habe da schonmal rumprobiert:

    class BaseFuncPtrTest
    {
    public:
    	BaseFuncPtrTest(){}
    	void AFunc(const char* strComment){ cout << "Base::PublicFunc called from " << strComment << "\n" << endl; }
    	void BFunc(const char* strComment){ cout << "Base::PrivateFunc called from " << strComment << "\n" << endl; }
    	void CFunc(const char* strComment){ cout << "Base::ProtectedFunc called from " << strComment << "\n" << endl; }
    };
    
    //in main
    BaseFuncPtrTest aAbsBase;
    typedef void(BaseFuncPtrTest::*pBaseFunc)(const char*);
    pBaseFunc pFuncOne = &BaseFuncPtrTest::AFunc;
    pBaseFunc pFuncTwo = &BaseFuncPtrTest::BFunc;
    pBaseFunc pFuncThree = &BaseFuncPtrTest::CFunc;
    
    (aAbsBase.*pFuncThree)( "Hello World" );//aufruf
    

    Was ist diesere Member-funktionspointer eigentlich? Ein Offset zur Basisadresse des Objectes? Leider scheint es ja verboten zu sein einfach mit Funktionspointern wild umherzurechnen. Inwieweit ist die Ausrichtung der Memberfunktionen im Objekt dort überhaupt standardisiert?
    Ich spare hier einmal virtuelle Funktionen aus, da ich bereits weiß, das dem dort nicht so ist.



  • Was ist diesere Member-funktionspointer eigentlich?

    Ein "Ding" das gebunden an ein passenden Objekt zu einem "aufrufbaren Ding" wird. Viel genauer definiert der Standard das leider nicht.
    Der Standard definiert nur, wie sich ein Memberfunktionszeiger (MFP) verhalten muss (welche Bindungen sind legal, welches Ergenis hat welcher Aufruf).

    Es ist Sache des Compiler in einem MFP all die Informationen zu speichern, die er zum Aufruf der referenzierten Memberfunktion braucht.
    Wie er das macht bleibt ihm überlassen.

    Eine Mögliche Strategie ist z.B. die Abbildung eines MFPs auf eine Struktur mit drei Elementen:
    1. Basisadresse
    2. Virtual-Table-Index
    3. This-Pointer-Adjustment

    Für nicht virtuelle Funktionen ist 1. dann einfach die Adresse der Memberfunktion. 2. und 3. können ignoriert werden.
    Für virtuelle Funktionen in einer Single-Inheritance-Hierarchie wäre 1. die Adresse der vtable. 2. der Index der Funktion in der vtable und 3. kann ignoriert werden.
    Im MI-Fall enthält 3. die Anfangsadresse des Teilobjekts (oder der Offset von der Startadresse des Gesamtobjekts) für das die Memberfunktion aufgerufen werden soll.

    Ein MFP lässt sich aber auch über einen einzelnen Zeiger implementieren. In diesem Fall würde der Zeiger immer auf eine thunk-Funktion zeigen. Thunk-Funktionen sind dann kleine Funktionen die vom Compiler generiert werden und die entsprechenden Anpassungen am this-Zeiger vornehmen und dann die passende Funktion aufrufen.

    Letztlich kannst du dich aber auf keine Implementationsstrategie verlassen, da dir der Standard keinerlei Garantien in dieser Richtung bietet.

    Leider scheint es ja verboten zu sein einfach mit Funktionspointern wild umherzurechnen.

    Richtig. Der Standard fordert nur, dass ein beliebiger MFP in einen anderen MFP gecastet werden kann und das der Cast zurück zum Orginal-MFP-Typ wieder den Original-MFP liefert (was Compiler wie der VC 6.0 zum Beispiel nicht können). Ein konformer Compiler kann einen MFP also intern implementieren wie er will solange er eine Abbildung zwischen den verschiedenen internen Repräsentationen definiert und damit den Cast zwischen zwei beliebigen MFP-Typen möglich macht.



  • Hallo,
    willst etwas in der Art?

    #include <boost/function.hpp>
    class AClass
    {
      typedef boost::function1<void, AClass*> SimpleHandler;
      typedef std::vector<SimpleHandler> SimpleHandlers;
    public:
      typedef SimpleHandlers::const_iterator HandlerIter;
      void OnDoSomething() {cout << "DoSomething" << endl;}
      void OnDoOnotherthing() {cout << "DoAnotherThing" << endl;}
      void OnDoLastthing() {cout << "DoLastThing" << endl;}
      static HandlerIter handlersBegin() {
        return handlers_.begin();
      }
      static HandlerIter handlersEnd() {
        return handlers_.end();
      }
    private:
      static SimpleHandlers initHandlers() {
        SimpleHandlers ret;
        ret.push_back(SimpleHandler(&AClass::OnDoSomething));
        ret.push_back(SimpleHandler(&AClass::OnDoOnotherthing));
        ret.push_back(SimpleHandler(&AClass::OnDoLastthing));
        return ret;
      }
      static SimpleHandlers handlers_;
    }; 
    
    AClass::SimpleHandlers AClass::handlers_ = AClass::initHandlers();
    
    int main() {
      AClass a;
      for (AClass::HandlerIter it = AClass::handlersBegin(); it != AClass::handlersEnd(); ++it) {
        (*it)(&a);
      }
    
    }
    

    Das Ganze kannst du durch ein paar Makros natürlich noch verschönern.



  • Erstmal danke für die ausführliche Erklärung. Ich komme eigentlich dazu, die Variante 2.) zu bevorzugen. Es ging mir darum eben:

    ret.push_back(SimpleHandler(&AClass::OnDoSomething));
    ret.push_back(SimpleHandler(&AClass::OnDoOnotherthing));
    ret.push_back(SimpleHandler(&AClass::OnDoLastthing));
    

    einzusparen, um das bei Änderungen nicht diesen part nicht immer nachziehen zu müssen (es sind halt ca. 80 OnDo Fkt.). Wenn ich im pre-compile eine neue Cpp - generiere, die dann das gleiche macht, kann ich schön nach Funktionen parsen, die der Signatur void OnDo...() folgen und somit diesen Code auch dynamisch erzeugen.

    Wenn es eine Möglichkeit gegeben hätte durch die Memberfunktionen zu iterieren hätte ich dann irgendwie sowas machen können (symbolischer code):

    void AddMemFktPtr( FuncPtr* pFirst, FuncPtr* pLast )
    {
      while( pFirst != pLast )...//alle dazwischen einfügen
    }
    

    ohne somit an eine bestimmte klasse gebunden zu sein, solange die MFPs fortlaufend im Speicher liegen und nicht Virtual sind. Das wäre halt bequemer gewesen.


Anmelden zum Antworten