Typenvariable Argumente, aber Boost.Any schlechter Stil?



  • Hallo,

    ich wollte mich mal etwas mit boost beschäftigen und habe somit eine IRC-Lib angefangen, welche auf Boost.Asio zurückgreift.
    Um einen EventManager zu realisieren, wollte ich eine Event-Klasse schreiben, welche jeweils einen Namen und beliebig viele, indexierte (std::vector) Argumente hat, welche natürlich jeweils verschiedene Typen aufweisen können (z. B. Channel, User, Mode, std::string, int, ...).
    Da dachte ich dann an Boost.Any. Natürlich ist auch Boost.Any kein Wunderwerk und erstellt intern natürlich Kopien, was für meine Belange gar nicht so gut ist... oder denke ich falsch? Und mit Pointern wollte ich eigentlich nicht umwerfen.

    Hier ist die Event-Klasse: http://42.holz.nu/6e793.png

    Ist das schlechtes Design?
    Wie würdet ihr das machen?

    Vielen Dank!



  • Wenn dann im Programm doch nur 6 verschiedene Events geworfen werden, warum nicht 6 Eventklassen?

    log<<"User "<<ev.params[7].get<User>().name()<<" hat den Channel "<<ev.params[3].get<Channel>().name()<< "betreten.\n";
    


  • Weil ich Custom-Events erlauben will.
    Die Library soll durch Module erweiterbar sein, d. h. Modul 1 soll bspw. das Event "Bla" feuern können, und alle anderen sollten es empfangen können.
    Das geht mit vordefinierten Klassen nicht so gut / gar nicht.



  • theliquidwave schrieb:

    Weil ich Custom-Events erlauben will.
    Die Library soll durch Module erweiterbar sein, d. h. Modul 1 soll bspw. das Event "Bla" feuern können, und alle anderen sollten es empfangen können.
    Das geht mit vordefinierten Klassen nicht so gut / gar nicht.

    Können alle anderen die Events denn überhaupt verarbeiten?
    Würde es reichen, eine gemeinsame Basisklasse zu haben?

    Wenns denn unbedingt sein muß, würde ich an variadic templates denken.



  • VS2012 kann leider keine Variadic Templates.
    Ja, können sie.
    Beispielsweise kennt das IRC von Haus aus kein UserAuthed-Event. Für das Quakenet würde ich dann ein Module schreiben, welche das feuert, damit andere das benutzen können.

    Wie soll das mit Templates funktionieren? Und auch, wie das aus deinem ersten Post (das, was du reineditierst hast) funktionieren soll, verstehe ich nicht? Dann würden ja jedes mal Kopien eines Users erstellt werden.



  • theliquidwave schrieb:

    VS2012 kann leider keine Variadic Templates.
    Ja, können sie.
    Beispielsweise kennt das IRC von Haus aus kein UserAuthed-Event. Für das Quakenet würde ich dann ein Module schreiben, welche das feuert, damit andere das benutzen können.

    Ja, und wer ein UserAuthed-Event benutzen will, muss aber schon die selbe "UserAuthedEvent.hpp" inkludieren. Und alle Events erben von Event.
    Kann mir nicht vorstellen, daß irgend ein Event-Empfänger (außer dem Logger) ein Event empfangen möchte und verarbeiten kann, von dem er nicht vorher die Struktur kennt.

    registerEventHaqndler("UserAuthedEvent",this);
    ...
    void processEvent(Event* e){
       UserAuthedEvent uae=static_cast<UserAuthedEvent*>(e);//dynamic_cas ist völlig überbewertet, löl. 
       //tuwas
    }
    

    Passen die signals von boost?



  • Okay, überzeugt. Für diesen Fall ausreichend.
    Aber wie würdest du es machen, wenn ein Pluginsystem hinzukommt, welches Module per dynamischer Bibliothek nachlädt? 😃



  • Verstehe nicht, inwiefern Plugins da das Geschäft erschweren, wenn immer Basisklassenzeiger herumgereicht werden.



  • Okay...
    Kann ich für den Fall eigentlich auch Boost.Bind benutzen?
    Das würde mich mal interessieren, beispielsweise dass so etwas möglich ist:

    class Bla
    {
    public:
        void OnUserAuthed(UserAuthedEvent *e)
        {
        }
    };
    
    // ...
    
    Bla b;
    EventManager x;
    
    x.RegisterListener("UserAuthed", boost::bind(&Bla::OnUserAuthed, &b, _1));
    

    Würde so etwas gehen? Wenn ja, wie muss die Signatur von EventManager::RegisterListener() aussehen?
    Vielen Dank 🙂



  • theliquidwave schrieb:

    wie muss die Signatur von EventManager::RegisterListener() aussehen?
    Vielen Dank 🙂

    Wenn der Aufruf steht, kannste den Compiler fragen. Einfach eine Funnktion mit falscher Signatur anbieten und compilieren und die richtige Signatur in der Fehlermeldung lesen.



  • Das geht so aber nicht, weil die std::function einen festen Call-Typ erwartet, daher kannst du kein Event-Spezifischen Typ als Parameter verwenden.



  • Schade. Also doch Pointer-Casting. Oder kann Boost.Function das?



  • Du kannst dir eine "Trampolin"-Funktion schreiben, die zwischen std::function (wozu Boost?) und deinem OnUserAuthed() liegt. Sie führt einen Downcast durch und gibt den Zeiger an OnUserAuthed() weiter.

    Dabei musst du natürlich Typsicherheit garantieren, sonst wird das mit dem Downcast nichts.



  • Wenn ich dich richtig verstanden habe, ist das Problem, dass du zur Compilezeit noch nicht weisst, welche Nachrichten kommen, und du in Zukunft ohne das das Hauptprogramm neu compiliert wird weitere Nachrichten hinzukommen?

    Falls ja, das exakt selbe Problem habe ich gelöst, indem ich die Nachrichten als XML-Text verschickt habe.

    Jeder kann sich als Nachrichten-Empfänger anhängen (via boost::signals). Wer was mit der Nachricht anfangen kann, bearbeitet sie, und schickt die Antwort ebenfalls per XML.



  • BadWolf schrieb:

    Wenn ich dich richtig verstanden habe, ist das Problem, dass du zur Compilezeit noch nicht weisst, welche Nachrichten kommen, und du in Zukunft ohne das das Hauptprogramm neu compiliert wird weitere Nachrichten hinzukommen?

    Falls ja, das exakt selbe Problem habe ich gelöst, indem ich die Nachrichten als XML-Text verschickt habe.

    Jeder kann sich als Nachrichten-Empfänger anhängen (via boost::signals). Wer was mit der Nachricht anfangen kann, bearbeitet sie, und schickt die Antwort ebenfalls per XML.

    Optimalerweise gibt es für die Nachrichten auch Dokumenttypdefinitionen. Außer bei so platten Sachen wie "Anzeigen der Nachricht" braucht der Empfänger ja eine Wissen darüber, was er zu erwarten hat.
    Also teilen sich Sender und Empfänger eine DTD zu dieser XML-Nachricht. Das Kernsystem muß davon überhaupt keine Ahnung haben, nicht die Geringte. Es sieht nur XML, ohne reinzugucken. Es können zwei Plugins trotzdem Nachrichten austauschen, sie sind sich eben zur Compilezeit über das Format einig.

    Na, mal hoffen, daß das nicht nach inner platform effect riecht.

    Headerfile statt DTD und Basisklassenzeiger statt XML und man hats als Sprachmittel. Ich verstehe Eure Motivation nicht. Odert redest Du gerade bloß davon, Nachrichten zu serialisieren, damit man sie übers Netz schicken kann?



  • Der einzige Unterschied besteht darin, dass es nicht zwingend abstürzt, wenn man sich doch nicht an die DTD hält, oder? Da wäre doch dynamic_cast auch nicht schlechter und würde keine Multi-Millionen-Zeilen XML-Bibliothek und einen DTD-Server benötigen.



  • Okay, dynamic_cast verlangt natürlich nach einem kompatiblen Compiler... Dann halt eine normale struct mit GUID-Message Ids und reinterpret_cast...



  • Decimad schrieb:

    Okay, dynamic_cast verlangt natürlich nach einem kompatiblen Compiler... Dann halt eine normale struct mit GUID-Message Ids und reinterpret_cast...

    static_cast



  • Okay, ich muss das jetzt ein für alle mal lernen. Macht es denn bei so einer Form von Downcast einen Unterschied, ob ich reinterpret_cast oder static_cast verwende? Kann die abgelittene Klasse irgendeine Form von Alignment verändern, dass reintepret_cast falsch ist? Okay, static_cast kann anspringen wenn es sieht, dass die Klasse gar nicht in Verbindung stehen. Aber kann es auch zur Laufzeit was verändern? Ich meine die Nachricht kann ja in keinem für diese Nachrichten spezifischen Adressraum sein, oder durch einen Cast dazwischen wandern.

    Danke schonmal für Aufklärung.

    Edit: Hrmmm, mehrfach-Vererbung vielleicht? Die konkrete Nachricht leitet von der Basisnachricht und etwas anderem ab, eventuell in anderer Reihenfolge, sodass die Basisnachricht nicht "vorne" im Speicher liegt?
    Edit2: Herrje, wie funktioniert in solch einem Fall eigentlich das delete? Okay, die Zerstörung geht dank virtuellem Destruktor ja problemlos, aber wie bekommt delete die Richtige Basisadresse raus? Gibt's da eine versteckte virtuelle Funktion, die die Basisadresse der konkreten Klasse ausspuckt?



  • reinterpret_cast garantiert nur, daß wenn man einen zeiger in einen beliebigen typen und wieder zurück castst, wieder das original erhält (platz muss natürlich genug da sein). über die gültigkeit des zwischenergebnisses habe ich keine ahnung, fürchte ich.


Anmelden zum Antworten