Vergleichsmöglichkeiten von std::tr1::function



  • Hallo zusammen,

    Ich habe gerade einige Probleme mit std::tr1::function . Genauer gesagt will ich eine Art Event-Callback-System einrichten, bei dem ich Empfänger in Form von function -Objekten übergeben kann. Nun wäre es natürlich gut, man könnte angemeldete Empfänger wieder abmelden. Dass Funktionen nicht direkt verglichen werden können, ist mir bewusst, jedoch scheint es teilweise Workarounds zu geben.

    Einer davon ist das Memberfunktionstemplate target() , mit dem man – sofern man den richtigen Typen übergibt – einen Zeiger auf das interne Funktionsobjekt erhält. Doch so ganz habe ich das nicht verstanden; auf den meisten Seiten, die ich gefunden habe (z.B. 1 oder 2) sind keine komplexeren Codebeispiele zu finden.

    #include <functional>
    #include <iostream>
    
    double half(int a)
    {
    	return a / 2.;
    }
    
    int main()
    {
    	typedef double half_t(int);
    	typedef std::tr1::function<half_t> func_t;
    
    	half_t* ptr = &half;
    
    	func_t a = ptr;
    	func_t b = ptr;
    
    	std::cout << a.target<half_t*>() << std::endl;
    	std::cout << b.target<half_t*>() << std::endl;
    }
    

    Ich erhalte unterschiedliche Adressen, auch wenn ich std::tr1::cref(ptr) schreibe. Ist ja auch verständlich, da a und b verschiedene Funktionsobjekte speichern.

    Aber nun frage ich mich, inwiefern man target() dazu verwenden kann, um auf Gleichheit zu prüfen? Hat man beim Beispiel hier schon zu viele Informationen verloren? Wie könnte man dann zwei Funktionszeiger/-objekte direkt vergleichen, auch wenn kompliziertere Dinge wie bind() im Spiel sind?

    Ich habe mir überlegt, dass es für meine Events eventuell besser wäre, ich würde eine Art IDs (z.B. als Strings) einrichten, um Listener zu identifizieren. Allerdings interessiert es mich, wie weit man mit std::tr1::function gehen kann und wo die Grenzen liegen, von daher wäre ich sehr froh um Erklärungen.



  • Hast du dir schon Boost.Signals angesehen?



  • Bisher noch nicht, aber ich hab es mir jetzt ein wenig angeschaut. Scheint noch interessant zu sein, allerdings möchte ich für meine Bibliothek bei Std und TR1 bleiben.

    Boost.Signals löst das Problem so, dass einem eine boost::signals::connection -Instanz zurückgegeben wird, von der aus man freigeben kann. Das kommt ja meinem Ansatz der IDs schon recht ähnlich. Ist halt nicht sehr schön für den Benutzer, wenn er ständig IDs speichern muss, aber wohl der einzige Weg, sofern man mehr als Funktionszeiger vergleichen möchte. Jedenfalls vielen Dank für den Hinweis!

    Weiss vielleicht noch jemand Genaueres über die Anwendungsgebiete von std::tr1::function::target() ? Ich sehe immer noch nicht ganz, inwiefern das so nützlich sein soll.



  • Wäre immer noch aktuell... 🙂



  • Der Vergleich von Funktionsobjekten scheint schwierig zu sein, s.a.
    http://www.boost.org/doc/libs/1_36_0/doc/html/function/faq.html

    Und ein ähnliches Problem gibt es unter:
    http://stackoverflow.com/questions/89488/comparing-stdtr1function-objects



  • Nexus schrieb:

    Weiss vielleicht noch jemand Genaueres über die Anwendungsgebiete von std::tr1::function::target() ? Ich sehe immer noch nicht ganz, inwiefern das so nützlich sein soll.

    Was genau soll das bei target() überhaupt sein, was da zurückgegeben wird? Ich habe das mal mit einem Funktor getestet, jedoch konnte ich irgendwie keine Verbindung zwischen den Funktionsobject und dem Rückgabewert von target() finden.
    Ich hatte übrigens kürzlich ein ähnliches Problem, als ich einen Eventdispatcher mit Dispatchzielen die zur Laufzeit hinzugefügt und entfernt werden können erstellte.
    Ich habe dann allerdings boost.signals genommen, weil ich keinen direkten Weg zum Vergleich gefunden habe.



  • Th69 schrieb:

    Der Vergleich von Funktionsobjekten scheint schwierig zu sein, s.a.
    http://www.boost.org/doc/libs/1_36_0/doc/html/function/faq.html

    Und ein ähnliches Problem gibt es unter:
    http://stackoverflow.com/questions/89488/comparing-stdtr1function-objects

    Auf diese Seiten bin ich auch gestossen. Ich habe eigentlich überall die Aussage angetroffen, boost::function könne nicht generisch vergleichen. Allerdings hat mir der velinkte Text 2 den Eindruck verliehen, als könne target() da etwas bewirken. Es ist besser als nichts, aber bis jetzt scheint es mir nicht so, als könne man target() für ein sinnvolles Callbacksystem einsetzen – ich kann mich aber auch irren, deshalb dieser Thread.

    Tachyon schrieb:

    Was genau soll das bei target() überhaupt sein, was da zurückgegeben wird?

    So wie ich das verstanden habe, entweder der Funktionszeiger oder die Adresse des Funktionsobjekts, das intern verwendet wird (bzw. ein Nullzeiger bei falscher Typangabe). Und da liegt schon der Grossteil der Einschränkung: Soviel ich weiss, müssen die Callable-Objekte identisch sein, Gleichheit reicht nicht. Und ich frage mich, wie man std::tr1::function ein Objekt nur referenzieren lässt, ohne es zu kopieren. std::tr1::ref()/cref() haben hier nicht die erwünschte Wirkung...

    Tachyon schrieb:

    Ich hatte übrigens kürzlich ein ähnliches Problem, als ich einen Eventdispatcher mit Dispatchzielen die zur Laufzeit hinzugefügt und entfernt werden können erstellte.
    Ich habe dann allerdings boost.signals genommen, weil ich keinen direkten Weg zum Vergleich gefunden habe.

    Boost.Signals scheint ein guter Weg zu sein, aber momentan möchte ich darauf wie gesagt noch verzichten. Ich werde mir wohl was Einfaches nachbauen, das in die Richtung geht. Ich habe da eine kleine Klasse mit einem std::list::iterator im Sinne...

    Meine zuerst erwähnte Möglichkeit bestand darin, direkt beim Hinzufügen eine ID zu übergeben, nach der gelöscht werden konnte. Aber dadurch müsste man auch für die Fälle eine ID übergeben, in denen man gar nicht löschen will, zusätzlich kann es schnell kompliziert für den Benutzer werden. Am anderen Ansatz finde ich schön, dass man den Rückgabetyp einfach ignorieren kann, wenn man keine Löschung plant.

    Wenn ihr weitere Ideen oder Vorschläge habt, bringt sie ein! :xmas1:



  • Nexus schrieb:

    ...

    Vielleicht wäre eine unordered_map mit ensprechenden IDs etwas fixer als eine Liste. Einfacher wäre das vermutlich auch.



  • Tachyon schrieb:

    Vielleicht wäre eine unordered_map mit ensprechenden IDs etwas fixer als eine Liste. Einfacher wäre das vermutlich auch.

    Ok, ja, das wäre auch eine Alternative. Mit einer Liste hätte ich allerdings ebenfalls konstante Zeitkomplexität, da man das Element zu einem Listen-Iterator schnell entfernen kann. Bei std::tr1::unordered_map kann ich mir dabei eher vorstellen, dass etwas Overhead im Spiel ist (auch vom Speicherbedarf her).

    Für einen Boost.Signal-ähnlichen Mechanismus habe ich an eine verkettete Liste gedacht, weil die Iteratoren gültig bleiben. Das heisst, wenn man dem Aufrufer ein Connection-Objekt zurückgibt, das gekapselt einen List-Iterator besitzt, kann man dieses der Event-Klasse übergeben, welche dann die Löschung aufgrund des Iterators vornimmt.

    Ich experimentiere noch etwas herum. Momentan sehe ich gerade keinen Weg ohne friend , den Iterator gegenüber dem Benutzer zu verbergen.



  • Inzwischen habe ich es mit dem genannten Listen-Iterator-Ansatz implementiert. Die wesentliche Schnittstelle der Event-Verwaltungsklasse sieht so aus:

    class MyClass
    {
        public:
            Connection AddXYListener(const XYListener& Listener);
            void       RemoveXYListener(Connection& Con);
    };
    

    Nun komme ich an den Punkt, an dem ich entscheiden muss, wie der Benutzer mit Connection-Objekten umzugehen hat – genauer, wie viel Sicherheit ich ihm gewähre. Dabei ziehe ich drei Möglichkeiten in Betracht:

    • Normale Kopiersemantik: mehrere Connections zum selben Listener existieren unabhängig voneinander. Da so sehr leicht Inkonsistenzen (z.B. Connections, die eigentlich bereits ungültig wären, aber noch als gültig gekennzeichnet sind) auftreten können, gefällt mir dieser Weg nicht besonders.
    • Move-Semantik: Es gibt immer nur eine gültige Connection zum selben Listener. Der Besitz wird bei Kopien übertragen. Ist zwar sehr effizient und für die meisten Fälle okay, allerdings kann es Probleme geben, wenn übliche Voraussetzungen an die Kopiersemantik gestellt werden (z.B. in Containern).
    • Shared-Semantik: Jede Connection besitzt Informationen über die anderen existierenden Instanzen. Ist zwar am flexibelsten, aber auch am teuersten wegen Reference Counting und dynamischer Allokation. Eigentlich wäre das alleine nicht allzu schlimm, jedoch wird der Rückgabewert von AddXYListener() möglicherweise oft gar nicht gebraucht, da man hinzugefügte Listener nicht mehr oder gleich alle auf einmal entfernen möchte (Destruktor oder ClearXYListener() -Funktion).

    Welchen Weg würdet ihr wählen?



  • Hat noch jemand Vorschläge? Ich tendiere momentan zu geteilten Besitzverhältnissen, weil dieser Weg sehr komfortabel und sicher ist. Aber grundsätzlich lasse ich die Benutzer ungerne für Dinge zahlen, welche sie nicht brauchen...


Anmelden zum Antworten