Verständnisproblem bei libsigc++



  • Hallo zusammen,

    ich versuche grade mich ein wenig mit der libsigc++ vertraut zu machen.
    Nutzen möchte ich das ganze später um einen Datenfluss zwischen verschiedenen
    Modulen zu realisieren, so nach diesem Prinzip: http://images.gamedev.net/features/reviews/maya6/Figure2.jpg

    Der Gedanke dabei ist folgender: Jedes Modul besitzt ein Signal, und eine Funktion, sowas wie

    modul01.go()
    

    Wenn ein Modul alle Berechnungen abgeschlossen hat, löst es sein Signal aus, welches an die go()-Funktion des nächsten Modules gebunden ist. Das Prinzip an sich funktioniert auch, Probleme habe dabei, die Signale mit der jeweiligen nächsten Funktion zu verbinden.

    Im moment sieht das ganze etwa so aus:

    modul01.sig_pass_data.connect( sigc::mem_fun(modul02, &modul::go) );
    

    Soweit ich das richtig verstanden hab erwartet die connect()-Funktion einen Funktor, welcher von sigc::mem_fun() aus dem jeweiligen Objekt und einem weiteren Argument, welches ich nicht verstehe, zusammengesetz wird.

    Da man später die Module je nach bedarf zusammenschalten können soll ist beim kompilieren natürlich noch nicht klar, welches Signal woran gekoppelt wird.
    Auch würde ich gerne später jedem Modul eine connect()-Funktion geben, der man nur mitteilen muss, an welches Objekt sie das Signal binden soll.
    Was also her muss, ist eine Lösung, die in der Lage ist dieses zweite Argument dem ersten anzupassen. (Das erste könnte ja recht einfach als mit einem Pointer übergeben werden)

    Meine Fragen sind also:
    ->Was ist das zweite Argument?
    ->Kann ich das Argument aus den Informationen, die ich über ein Objekt herausfinden kann erhalten?

    Oder wäre vielleicht ein komplett anderer Weg sinnvoller?

    Danke schonmal, und viele Grüße!
    Tobi



  • PixelRealm schrieb:

    Oder wäre vielleicht ein komplett anderer Weg sinnvoller?

    Du musst wohl irgendwo eine speichernde Struktur haben, in der alle Module liegen. Sonst hättest du nicht die Möglichkeit, verschiedene Module hinzuzufügen und die Ablaufreihenfolge zu ändern. Gib doch dieser Struktur Methoden, um die Ablaufreihenfolge anzupassen und eine initiale go()-Funktion, die dann der Reihe nach alle go() der Modulinstanzen aufruft. Das geht dann komplett ohne sigc++.

    Dieses Signal/Slot-Konzept macht dann Sinn, wenn es möglich sein soll, mehrere Objekte, die an unterschiedlichen Orten (Objekten) gespeichert sind, an ein Signal zu hängen, eben auch dynamisch während der Laufzeit. Bei dir wird das Konzept nur verwendet, um eine Ablaufreigenfolge zu definieren. Das geht auch ohne diesen Umweg.



  • Ansich bin ich von der Idee, das sich das Netz sozusagen selber managed, indem jedes Modul nur weiß, woher es seine Daten beziehen soll, und wann es losrechnen soll recht angetan, weil man sich dabei nicht darum kümmern muss dass es läuft. Auch sind Verzweigungen oder mehrfache Ein/Ausgänge verhältnismäßig leicht zu realisieren.

    Das man dafür die Signale nicht benötigt, erscheint mir inzwischen auch logisch.
    Wenn ich zwei Module anlege, von denen ich weiß, dass sie z.B diese go()-Funktion haben, dann kann ich diese Funktion ja von überall nach diesem Prinzip aufrufen:

    Modul* Pointer_Auf_Modul = &Modul01;
    Pointer_Auf_Modul->go();
    

    Wo es jetzt wieder scheitert ist die Deklaration des Pointers, der muss ja vom Typ "Modul" sein. Das weiß ich aber erst, wenn alles schon kompiliert ist. Mit void* funktioniert das nicht. Gibts da ne Möglichkeit, dieses Problem zu lösen, oder habe ich falsche Vorstellungen davon, wie so ein Zeiger funktioniert?



  • Du kannst keine Funktion aufrufen, ohne den Typ zu kennen. Ich denke du möchtest Polymorphie.

    class Module {
        Module* next_;
    public:
        Module()
         : next_(0)
        {}
    
        virtual ~Module() {
            delete next_;
        }
    
        void setNext(Module* m) {
            m->next_ = next_;
            next_ = m;
        }
    
        virtual void go()=0;
    };
    
    void
    Module::go() {
        if( next_ ) next_->go();
    }
    
    class NewMod : public Module
    {
    public:
        void go() {
            cout << "NewMod::go()" << endl;
            Module::go();
        }
    };
    

    Es gibt aber ein kleines Problem: Anwachsender Stack! Der Stack von go() wird erst aufgeräumt, wenn das go() von next fertig ist. Wenn du viele Module verkettest, und die teilweise richtig viel Speicher brauchen (z.B. jedes Module sein eigenes monster DOMTree), kann das problematisch werden - in heutigen Zeiten sicher weniger.
    Vorteil: schneller Ablauf der Berechnungen, da die Aufräumarbeiten an einem Stück nach den ganzen Berechnungen ablaufen 😉
    Das selbe gilt aber auch für sigc++ (resp. boost::signal).
    Kann man natürlich umgehen, indem man die Logik in nen eigenen Scope legt.

    class NewMod2 : public Module
    {
    public:
        void go() {
            {
                dom_document doc("big_xml.xml");
                /* usw*/
            }
            Module::go();
        }
    };
    

    Ein iterativer Ansatz würde so ausschauen.

    std::vector<Module*> modules;
    foreach(Module* mod, modules) {
        mod->go();
    }
    

    Was anderes fällt mir nicht ein...



  • Ich hab mir Polymorphie mal angeguckt und ein bisschen experimentiert, und ich glaube das Problem ist gelöst. Und zwar so:

    Ich habe eine Klasse angelegt die Nur aus Konstruktor, Destruktor und eine Funktion, go(), besteht. go() ist als virtual deklariert.
    Alle Modulklassen erben von dieser Klasse. Der Vorteil: ich kann, wenn ich einen Pointer vom Typ der Vaterklasse anlege, mit diesem auf alle Instanzen der Kindklassen zeigen lassen, und auch deren go()-Funktion aufrufen.
    So brauch jedes Modul nur zu wissen, welches Modul an es selber angeschlossen ist, um diesem sozusagen das Startzeichen zukommen zu lassen...

    Wie viel Speicher das Programm am Ende verbrauchen wird kann ich nicht abschätzen, vor allem weil ich erstmal testen wollte ob diese Netzwerk-Idee überhaupt funktioniert.
    Vielen Dank aber auf jeden Fall für die Hilfe und das entscheidende Stichwort! 🙂



  • Du solltest den Destruktor der Klasse unbedingt virtuell machen, wenn diese polymorph ist (also andere virtuelle Funktionen enthält). In abgeleiteten Klassen musst du den Destruktor nicht explizit deklarieren, er ist automatisch virtuell, falls der Destruktor einer Basisklasse bereits virtuell ist.


Anmelden zum Antworten