pointer auf member function



  • hallo leute

    hab vor paar jahren eine signal klasse geschrieben.
    wollt nun mal wissen ob die so standardkonform ist.

    struct generic_class
    {
       auto function(void) -> void;
    };
    
    class signal
    {
       public:
          using function = auto (generic_class::*)(void*) -> void;
    
          signal(void) noexcept : m_object(nullptr), m_function(nullptr) { }
          signal(const signal&) = delete;
          signal(signal&&) = delete;
          ~signal(void) noexcept { }
    
          auto operator=(const signal&) -> signal& = delete;
          auto operator=(signal&&) -> signal = delete;
    
          template<class FUNC>
          auto set(void *object, FUNC func) -> void;
    
          auto call(void *sender) -> void { (m_object->*m_function)(sender); }
    
          auto is_valid(void) const noexcept -> bool { return m_function != nullptr; }
          auto is_invalid(void) const noexcept -> bool { return m_function == nullptr; }
    
       private:
          generic_class *m_object;
          function m_function;
    }; /* class signal */
    

    hab die klasse bisher nur mit VC++ verwendet, wo es nie probleme gab.
    will sie nun erweitern und da wuerde mich eben interessieren ob das so mit dem standard in ordnung geht.
    btw: wie sieht es eigendlich damit aus wenn ich sie ueber dll grenzen hinweg verwenden will ?

    Meep Meep


  • Mod

    Nur weil's Benzin billig ist knallst du überall ein auto rein?



  • bin sammler



  • Arcoth schrieb:

    Nur weil's Benzin billig ist knallst du überall ein auto rein?

    Es gibt schon Argumente dafür diese Schreibweise konsequent zu verwenden
    (auch wenn sie aus dem Code hier nicht so ersichtlich werden):

    - Einheitliche Notation: Man findet den Rückgabetypen immer an der selben Stelle.
    Da man manchmal die neue Schreibweise benutzen muss (decltype) oder möchte (weil der Klassen-Scope,
    den man bei der Angabe des Rückgabetyps hat, redundanten Code spart, und so den Code etwas übersichtlicher macht)
    kann man argumentieren, dass der andernfalls zwangsläufige Notations-Mix (Rückgabetype mal vorn, mal hinten) der Lesbarkeit schadet.

    - Die Namen der Memberfunktionen stehen so alle in der selben Textspalte, egal wie lang der Name des Rückgabetyps ist.
    Manche Leute würden sagen, dass es die Übersichtlichkeit fördert, wenn man beim Lesen nicht erstmal den Anfang jeder Zeile
    parsen muss, nur um herauszufinden, wie denn die Funktion eigentlich heisst, die man sich da gerade anschaut.

    Das einzige Gegenargument ist das zusätzliche auto , das man schreiben muss - von dem natürlichen Unbehagen mal abgesehen,
    das wahrscheinlich einige dabei verspüren, weil sie es anders gewohnt sind 😉

    Finnegan



  • Ich verwende immer noch den gute alte davor-schreiben-stil. Ansonsten sieht das schon ziemlich hacky aus, std::function passt hier wohl besser.



  • roflo schrieb:

    Ich verwende immer noch den gute alte davor-schreiben-stil. Ansonsten sieht das schon ziemlich hacky aus, std::function passt hier wohl besser.

    ja ich wollte ursprünglich auch std::function verwenden. was mich da aber davon abgebracht hatte, war das std::function recht groß ist.
    sizeof(std::function<void(void*)>) zeigt mir 40 bytes an, und mein signal braucht grad mal 8 bytes.
    nachdem ich aber nur member function pointers brauche, bin ich auch nicht bereit dafuer das 5-fache zu zahlen, vorallem dann nicht,
    wenn mein signal standardkonform ist.
    wenn ich als 64 bit kompiliere ist der unterschied nicht mehr ganz so groß: 64 bytes zu 16 bytes.
    ansonsten muss ich sie fallen lassen.
    gibts von std::function auch sowas wie eine light version ?

    Meep Meep



  • Solange du nicht Tausende von signalen hast und nicht den üblichen Desktop-Rechner abdecken willst, ist std::function die beste Wahl 🙂
    Kann auch sein, dass es in der release-version kleiner ist.



  • mit VC 2015 sind die größen in debug und release die selben.
    jedes object hat 63 signale, aber ich hab 16384 objekte.
    und da macht es schon sehr viel aus



  • Wo ist die Implementation dieser Funktion?

    template<class FUNC>
    auto set(void *object, FUNC func) -> void;
    

    Die wäre mal interessant. Vor allem weil es einen void Pointer für das Objekt nimmt und die Funktion als Template. Wie speicherst du das dann in deinem Member Function Pointer der einer festen Signatur folgt?

    Meep Meep schrieb:

    mit VC 2015 sind die größen in debug und release die selben.
    jedes object hat 63 signale, aber ich hab 16384 objekte.
    und da macht es schon sehr viel aus

    Es geht. Macht also ~1Mio Signale und damit 40MB für std::function und 8MB für dein Signal.



  • hi sebi, hier die signal::set

    template<class FUNC>
    inline auto signal::set(void * object, FUNC func) -> void
    {
       m_object = reinterpret_cast<generic_class*>(object);
       m_function = reinterpret_cast<function>(func);
    }
    


  • Das ist mit großer Sicherheit nicht Standardkonform. Pointer to Member Functions sind komische Tiere. Ich finde zwar gerade nichts im Standard aber dein Code funktioniert wahrscheinlich nicht wenn virtual im Spiel ist oder wenn die Größe der Parameter/Rückgabewert nicht stimmt.



  • virtuelle methoden funktionieren genau so. da gibts keine probleme.

    das die größen der parameter/rückgabewert stimmen muss, ist ja logisch.



  • Mal ein Beispiel bei dem es kaputt geht:

    class A
    {
      int x = 5;
    public:
      void foo(void* sender)
      {
        cout << "foo " << x << endl;
      }
    };
    
    class B : public A
    {
    public:
      virtual void bar()
      {
      }
    };
    
    int main()
    {
      B b;
      signal x;
      x.set(&b, &B::foo);
      x.call(NULL);
    }
    

    Das gibt bei mir foo 17599292 (oder irgendeine andere Zahl aus). Zugegeben ist das jetzt nicht wegen deiner Member Function Pointer sondern wegen dem reinterpret_cast auf das Objekt. Dadurch das die Klasse B aufeinmal virtuelle Funktionen hat gibt es einen Offset zwischen einem Pointer auf A und Pointer auf B des gleichen Objekts. Ließe sich auch mir Mehrfachvererbung erreichen. Vermutlich kann man auch noch andere Konstruktionen finden die nicht funktionieren.



  • Ach.. Sieht ja (fast) genauso aus wie die Klasse, die ich mir letztens geschrieben habe (bis auf die Lambda-Syntax (erst auto und dann noch den Rückgabetyp - das ist doch doppelt gemoppelt), und noexcept fehlt bei mir auch (ist das für den Compiler wirklich wichtig, oder nur für den Menschen?)).

    Ist ja auch kein Wunder. Denn, 1., wie will man das sonst machen*, wenn man 2., nicht wirklich verstanden hat, was hinter boost::function und boost::bind (od std::..) steckt (so wie ich). In wxWidgets gibt es auch eine Bind<>()-Funktion, mit der man an bel. Funktionen und Methoden binden kann. Wird wohl so ähnlich laufen, aber wie genau - da bin ich überfragt.
    Vielleicht kann mir das jemand erklären, ohne jetzt den Thread hier einnehmen zu wollen..

    *Allerdings habe ich irgendwo gelesen, dass man Mathodenzeiger eigentlich nur zum Zwischenspeichern umcasten, und zur Benutzung wieder rückcasten darf - sehr flexibel...

    Erstmal zurück zu der bereits implementierten Klasse..
    Ich war/bin mir nicht sicher wie regelkonform das ist. Nach meiner Vorstellung ist eine Methode einfach eine Funktion irgendwo im Speicher, die als zus. verstecktes Arg. den this-Ptr ihrer Klasse übergeben bekommt - egal zu welcher Klasse die Methode jetzt konkret gehört. Daher verstehe ich nicht, warum man beim Methodenzeigern die Klasse mit berücksichtigen muss.
    Anders gesagt, sehe ich nicht, warum der Klassenname zur Funktionssignatur gehören sollte...
    Eigentlich gibt es doch nur Funktionszeiger (die ohne verstecktes "this"), und Methodenzeiger (die mit "this"), aber nicht Methodenzeiger von Klasse A, Methodenzeiger v. Klasse B, etc..
    Also nach dieser Vorstellung wäre es kein Problem, A::MethodenPtr in B::MethodenPtr zu casten (restliche Signatur stimme überein).
    Aber so 100%ig sicher bin ich mir nicht, da ich ja nicht weiß, ob meine Annahme immer (für alle Compiler/Plattformen/..) gilt.

    An virtuelle Methoden habe ich noch garnicht gedacht. Da habe ich auch überhaupt keine Vorstellung davon, wie ein Methodenzeiger damit umgeht.
    Könnte ja sein, dass das der Grund dafür ist, warum zw. A::MethPtr und B::MethPtr unterschieden wird, würde also die obige Annahme stören.
    Kann mir jemand erklären, warum gecastete Methodenzeiger mit virtuellen Methoden klarkommen, wenn sie es denn tun..?!

    Vielleicht zum Schluss noch kurz das Grundproblem: Es geht hier doch nur darum, aus einer Senderklasse heraus eine, bei der Implementierung nicht weiter bekannte, Empfängerklasse anzusprechen (stimmts?). D.h. meine Signal-Klasse kann keine Infos über den Empfänger besitzen. Alle Infos müssen erst bei der Verbindung von Sender und Empfänger durch eine gemeinsame Oberklasse bekannt gemacht werden. So weit so richtig? - Falls ja, nochmal die Frage: Wie will man das sonst machen?!
    Die Alternative, also ohne Signal-Klasse, wäre eine Sendefunktion für jede mögliche Empfängerklasse zu implementieren, oder sich eben auf genau eine Empfängerklasse zu beschränken - also keine wirkliche Alternative.
    Und noch eine: Warum gibt es dafür kein schönes Sprachmittel?!



  • Ich hatte vorhin zu Member Function Pointer auch noch diesen Artikel gefunden: https://blogs.msdn.microsoft.com/oldnewthing/20040209-00/?p=40713 Ich habe ihn auch nur überflogen aber eine schonmal interessante Tatsache war, dass Memberfunction Pointer unterschiedliche Größe (abhängig von der Klasse) haben können.

    Die Technik die std::function benutzt nennt sich übrigens Type Erasure. Find gerade keinen guten Übersichtsartikel aber die Grundidee ist eine abstrakte Basisklasse mit der gewünschten Funktionalität (also hier eine Funktion mit einer bestimmten Signatur) zu haben und dann eine Template Klasse die von dieser erbt. Durch den Template Parameter in der abgeleiteten Klasse kann man beliebiges Zeug speichern ohne das der Typ nach außen sichtbar ist weil man sich das Ganze nur als Pointer auf die Basisklasse speichert.



  • Wie wärs einfach mit einer abstrakten Basisklasse, von der der Signalhandler erbt und eine 'OnSignal' methode überschreiben kann?



  • @sebi
    ich waere ehrlich gesagt nie auf die idee gekommen die klasse so zu verwenden.
    ich verwende sie eigendlich nur um schreibarbeit zu sparen, wie sie mir der abstarkten basisklasse aus roflo´s vorschlag entstehen wuerde


Anmelden zum Antworten