Verschiedene Objekte in einem Vektor



  • nein, du hast mich falsch verstanden. (oder ich habe es falsch erklärt).
    Aber versuche ich mal anhand eines Beispiels:

    Sagen wir mal, ich schreibe eine Gui-Lib. So mit Fenstern, vielen tollen verschiedenen buttons, usw.
    Diese Buttons und andere Controls befinden sich logischerweise innerhalb von Fenstern.
    Man kann auch als User der Lib durch alle Steuerelemente, die sich in einem Fenster befinden iterieren. Mal angenommen ich meine Lib unterstüzt auch Bild-Controls, Also steuerelemente, die Bilder enthalten können. Wenn ich darauf zugreife. Kann ich z.b. die Funktion, ImageControl* getImages() aufrufen.

    Obwohl ich da tausende von steuerelementen erstellt habe, habe ich die Lib auch so geschrieben dass man eigene controls implemnentieren und verwenden kann. So kommt z.b. ein User auf die Idee ein Steuerelement zu programmieren, dass z.b. seinen namen als 3d animation darstellt. so ein steuerelement gibt es ja bei mir nicht.

    Entweder könnte er mir jetzt den Quelltext (oder mindenstens den Header) für sein Steuerelement zu schicken. Dann ich dann bei meinem Fenster eine Methode mit dem Namen getNameDisplayControls() schreiben, die dann alle Controls mit diesem Typ zurückgibt, die sich auf dem aktuellen Fenster befinden. (Meine Lib ist closed-source.)
    Oder ich mache soetwas nicht und biete einfach die Funktion Control* getControls() an, die einfach alle Steuerelemente zurückgibt. Aber soetwas darf ich ja deiner meinung nach nicht machen. Meiner meinung nach wäre die erste Möglichkeit sinnlos.
    Und nein, den Quelltext der Lib will ich nicht veröffentlichen.



  • Dann ist Objektorientierung noch nicht bei dir in Fleisch und Blut übergangen. Ich greife mal dein Beispiel mit der 3D-Darstellung auf. Nichts einfacher als das!

    class GuiBasisElement
    {
        public:
           virtual void draw(GrafikContext g);
    };
    

    So, das ist gängige Praxis! Jedes GUI-Element zeichnet sich SELBST. Jedes Element in den Containern wird einfach durch iteriert und die draw-Methode aufgerufen. draw() ist in dem Fall ein Interface-Bestandteil. So, jetzt kommt ein x-beliebiger User aus Asien der sein eigenes neues 3D-Control schreiben will. Was macht er? Er implementiert einfach die draw-Methode!!!!!

    class Mein3DControl : public GuiBasisElement
    {
       public:
          virtual void draw(GrafikContext g)
          {
                // 3D-Grafikcode zeichnet in den Grafikcontext g.
          }
    };
    

    Deine Lib, die nichts von Mein3DControl weiß, und auch nichts wissen muß, ruft einfach draw auf. Was draw macht, ist sowas von banane, das glaubt man nicht. Ob jemand jetzt 2D-Grafik zeichnet, 3D-Grafik zeichnet, nichts zeichnet interessiert hier nicht. Du (!) hast eine Schnittstelle für andere Programmierer vorgegeben die sagt: "alles was dein Control darstellt, sollst du bitte in draw implementieren." Punkt!

    Damit das Control weiß, wo es hinzeichnen muß, übergibt man ihr einen Context (Zeichenfläche, z.B. einen Fensterbereich).

    Auch das hier wäre möglich:

    class AppleQuickTimePlayer: public GuiBasisElement
    {
       public:
          virtual void draw(GrafikContext g)
          {
                // Bilder des MPEG-Movies darstellen bzw. in den Grafikcontext zeichnen...
          }
    };
    

    D.h. selbst Movieplayer kann man als GuiElement darstellen. Ist nur eine Frage der Implementierung. Nichts anderes machen z.B. die Plugins für InternetExplorer oder Firefox. Der QuickTime-Player implementiert einfach eine vorgegebene Draw-Schnittstelle, die MS oder Mozilla vorgibt, und schon sieht man einen Movie im Browser ablaufen. Aber IE und Firefox wissen nicht, das es ein Movieplayer ist. Warum auch?

    DAS ist Polymorphie. 💡 Deshalb sagen wir hier ja alle: dynamic_cast ist selten nötig! Definiert eine Schnittstelle in euren Basisklassen, und jeder kann diese implementieren. Der spezielle Objekttyp ist irrelevant für die Laufzeit.

    EDIT:

    vector<GuiBasisElement*> controls;
    
    controls.push_back( new Mein3DControl() );
    controls.push_back( new AppleQuickTimePlayer("C://move.mpg") );
    
    //...
    
    for(size_t i =0; i < controls.size(); i++)
        controls.at(i)->draw( fenster ); // fenster ist ein Grafikcontext
    


  • ich programmiere schon paar tage (auch objektorientiert), du erzählst mir da nix neues.

    Mein beispiel oben war vielleicht nicht so passend, aber ich versuche es nochmal:

    Nehmen wir diesmal an, du bist der gui-lib-programmierer (hast ja die klassen schon geschrieben).

    Ich programmiere einen AppleQuickTimePlayer:
    Im Gegensatz zu deinem, kann man meinen auch von außerhalb steuern:

    class MeinAppleQuickTimePlayer: public GuiBasisElement
    {
       public:
          virtual void draw(GrafikContext g)
          {
                // Bilder des MPEG-Movies darstellen bzw. in den Grafikcontext zeichnen...
          }
    
          void play();
          void stop();
          void irgendwas();
    };
    

    Nun will ich aus unerfindlichen Gründen MeinAppleQuickTimePlayer::play() aufrufen. Ich habe aber keinen Zeiger auf MeinAppleQuickTimePlayer gespeichert. Dafür aber das Fenster und ich weiß dass sich im Fenster ein Player befindet.

    Wie komme ich nun an MeinAppleQuickTimePlayer ohne dynamic_cast ???

    Wenn du meinst, dass so eine situation nie eintritt, dann kannst du bzw. ich noch mehr "funtionalität" in die lib einbauen:
    Man kann die Fenster mit steuerelementen als xml-Dateien auf die festplatte laden bzw. speichern. Diese Funktionalität ist nicht sichtbar für den end-user.
    d.h. er kann keinen Pointer auf MeinAppleQuickTimePlayer besitzen.



  • Nun will ich aus unerfindlichen Gründen MeinAppleQuickTimePlayer::play() aufrufen. Ich habe aber keinen Zeiger auf MeinAppleQuickTimePlayer gespeichert. Dafür aber das Fenster und ich weiß dass sich im Fenster ein Player befindet.

    play kann der jenige aufrufen, der den Player kennt. Oder sich über einen Callback oder Delegate diesen indirekt merkt. Dein Denkfehler ist, das du auf einen Player speziell zugreifen willst, obwohl du ihn nicht kennst. Das ist so, als wenn du in einem Polizeiauto die Sirene einschalten willst, obwohl du garkein Polizeiauto kennst oder vor dir stehen hast. Du willst anscheinend über Magie etwas tätigen, was völlig unmöglich ist.

    Wie programmierst du denn normalerweise? Du hast ein spezielles Objekt angelegt (den Player), weil du mit diesem etwas spezielles machen willst. Und du legst diesen in einen Container, der allgemeingültig ist. Du machst den Denkfehler, das du die Fenster-Klasse siehst, was sehr allgeimegültig ist und verwechselst es mit deiner spziellen Klasse. Meistens hat man aber eine spezielle Klasse, die den Kontext kennt (in dem Fall den Player). Und diese spezielle Klasse merkt sich den Player.

    Beispiel:

    class Anwendung
    {
       MeinAppleQuickTimePlayer *player;
       Fenster fenster; // da ist der allgemeine Container drin
    
    public:
       Anwendung()
       {
           player = new MeinAppleQuickTimePlayer(); // speziell
           fenster.add(player); // allgemein
       }
    
       void foo()
       {
           fenster.show(); //zeichnet unter anderem den Player, auch wenn es ihn nicht speziell kennt
           player->play();
       }
    };
    

    Das Fenster kennt zwar nicht die play-Methode, aber du selbst hast doch den Player angelegt und kannst ihn bedienen.

    Ich glaube, hier liegt der Irrtum vor, das nur der Container die GUI-Elemente kennen soll.

    Wenn es dir darum geht, das der Movieplayer vom User bedient werden soll: der Player kümmert sich selber um seine Play-Buttons. Drückt der User mit der Mouse drauf, reagiert der PlayButton selbst (genauso wie er sich auch selber zeichnet!).

    Das sind halt Erfahrungen, die man sammelt. Meistens ist die Sache einfacher und logischer als man denkt.



  • Hier noch ein paar Hinweise, die man sich später mal anschauen sollte:
    - Command-Pattern http://de.wikipedia.org/wiki/Command_Pattern
    - Function http://www.boost.org/doc/html/function.html (aka Delegates-Pattern)

    Diese dienen dafür, z.B. play aufzurufen, OHNE den Player speziell zu kennen!!! Aber es ist kein Cast nötig!!!

    Würde ich mich aber erst mit beschäftigen, wenn ich etwas mehr Erfahrung mit eigenen "Kreationen" gesammelt habe. Weil dann wird man erst diese Patterns zu schätzen wissen und man erkennt die Probleme die man damit löst. 😉



  • Artchi schrieb:

    play kann der jenige aufrufen, der den Player kennt. Oder sich über einen Callback oder Delegate diesen indirekt merkt. Dein Denkfehler ist, das du auf einen Player speziell zugreifen willst, obwohl du ihn nicht kennst. Das ist so, als wenn du in einem Polizeiauto die Sirene einschalten willst, obwohl du garkein Polizeiauto kennst oder vor dir stehen hast. Du willst anscheinend über Magie etwas tätigen, was völlig unmöglich ist.

    Wo genau ist da der Denkfehler? Wenn meine Fensterklasse funktionen bietet um über GuiBasisElement* zu itererieren. Was ist daran auszusetzten dass ich so auf den Player dann auch zugreife?
    Noch zusätzlich irgenwelche Zeiger zu speichern ist rendundant und kann Fehler verursachen. (Mein Player wird ohne meine Kenntnis durch einen anderen ersetzt.) Natürlich kann ich für alle möglichen Fälle Callbacks und co. anbieten. Aber warum sich unnötig arbeit machen, wenn ein einfacher dynamic_cast ausreicht?

    Wie programmierst du denn normalerweise? Du hast ein spezielles Objekt angelegt (den Player), weil du mit diesem etwas spezielles machen willst.

    Ich habe ja im letzten Absatz geschrieben, dass das Fenster aus einer XML-Datei erstellt wird. Die Xml-Datei wurde mit irgendeinem GUI-Editor angelegt. Ok, ich kann da callbacks beim laden zwischenschalten und so auf einen pointer kommen. aber dazu siehe oberen absatz.

    Ich glaube, hier liegt der Irrtum vor, das nur der Container die GUI-Elemente kennen soll.

    Wenn es dir darum geht, das der Movieplayer vom User bedient werden soll: der Player kümmert sich selber um seine Play-Buttons. Drückt der User mit der Mouse drauf, reagiert der PlayButton selbst (genauso wie er sich auch selber zeichnet!).

    Naja, der Player kann nicht alle Möglichkeiten abdecken, wie ihn der Benutzer verwendet. Der kann auf Player direkt klicken. Ok, den Fall braucht man nicht weiter zu betrachten. Der Player kann hier selbst dafür sorgen, dass er diese Aktionen wahrnimmt.
    Aber wenn ich meinen Player übers Netzwerk steuern will. will ich das mehr oder weniger direkt über player::play() machen. und nicht die maus dort positionieren, wo sich das entsprechende button befindet.

    Das sind halt Erfahrungen, die man sammelt. Meistens ist die Sache einfacher und logischer als man denkt.

    Ich habe den eindruck, dass du denks, dass ich noch neu in der programmierung bin. aber ich programmiere schon seit 8 jahren c++.

    obwohl..., das muss nichts aussagen. 😢



  • Wenn meine Fensterklasse funktionen bietet um über GuiBasisElement* zu itererieren. Was ist daran auszusetzten dass ich so auf den Player dann auch zugreife?

    Warum soll das Fenster (bitte darüber nachdenken!) die Play-Methode aufrufen wollen? WAS hat das Fenster mit dem Play zutun? Das Fenster kennt nur das GuiBasisElement-Interface.

    Noch zusätzlich irgenwelche Zeiger zu speichern ist rendundant und kann Fehler verursachen. (Mein Player wird ohne meine Kenntnis durch einen anderen ersetzt.)

    Nein, ebend nicht! Denn dein Pointer ist private! (man soll es nicht glauben) Meinetwegen machste halt keinen Pointer:

    class Anwendung
    {
        MeinAppleQuickTimePlayer player;   // kein Pointer
        Fenster fenster;
    public:
       Anwendung()
       {
           fenster.add(&player); // allgemein
       } 
    };
    

    Das mit der Redundanz ist kein Argument. Wir reden hier über OO-DESIGN und nicht über "Wie spare ich 4 Byte?".

    Aber wenn ich meinen Player übers Netzwerk steuern will. will ich das mehr oder weniger direkt über player::play() machen. und nicht die maus dort positionieren, wo sich das entsprechende button befindet.

    Auch dann kennt dein Netzwerk-CLient ein bestimmtes Interface. (selbst wenn es nur ein bestimmter String-Schlüssel ist) Es kann ein Wrapper sein, der am anderen Ende der Leitung wieder die play-Methode kennt. (z.B. durch erkennung des String-Schlüssels)

    Ich stelle fest, das du immer wieder andere Ausnahme-Szenarios vorbringst, sobald ich dir eine Nicht-Cast-Lösung aufzeige. Komisch, oder?

    Deine 8 Jahre sind schön und gut. Aber 1. lernt man nie aus. Und 2. sind gerade alte C++-Programmierer nicht DIE C++-Profis. Wenn ich sehe, das Projekte immer noch auf VC6 rumgurken und NICHT auf VC8 umsteigen können, weil sie nicht sauber C++ programmiert haben, sagt mir das sehr viel.

    Ich kenne dich überhaupt nicht, deshalb ist mein vorheriger Absatz allgemein gesprochen. Und das du C++-Neuling bist, muß ich davon ausgehen, da du nicht registriert bist, und somit keine Infos über dich habe.



  • Artchi schrieb:

    Warum soll das Fenster (bitte darüber nachdenken!) die Play-Methode aufrufen wollen? WAS hat das Fenster mit dem Play zutun? Das Fenster kennt nur das GuiBasisElement-Interface.

    Ich habe nie behauptet, dass ich auf die Play-Methode innerhalb der Fenster-Klasse zugreiffe. Das Fenster bietet Methoden um über die Elemente zu iterieren.

    Noch zusätzlich irgenwelche Zeiger zu speichern ist rendundant und kann Fehler verursachen. (Mein Player wird ohne meine Kenntnis durch einen anderen ersetzt.)

    Nein, ebend nicht! Denn dein Pointer ist private! (man soll es nicht glauben) Meinetwegen machste halt keinen Pointer:

    class Anwendung
    {
        MeinAppleQuickTimePlayer player;   // kein Pointer
    };
    

    Das mit der Redundanz ist kein Argument. Wir reden hier über OO-DESIGN und nicht über "Wie spare ich 4 Byte?".

    Bei Redundanz geht es mir nicht so sehr 4 bytes einzusparen, sondern um den zusätzlichen Aufwand um Sicherzustellen, dass der zeiger IMMER auf ein gültiges Objekt verweist.

    Auch dann kennt dein Netzwerk-CLient ein bestimmtes Interface. (selbst wenn es nur ein bestimmter String-Schlüssel ist) Es kann ein Wrapper sein, der am anderen Ende der Leitung wieder die play-Methode kennt. (z.B. durch erkennung des String-Schlüssels)

    Gegen Schlüssel jeglicher Art bin ich ziemlich alergisch. Sie erfordern abwärtskompatibilität. Vor allem, wenn man Komponenten von 3. herstellern verwendet. Ich will aber nicht abwärtskompatibel sein. Will auch jederzeit die schlüssel ändern können, wenn ich lustig danach bin.

    Ich stelle fest, das du immer wieder andere Ausnahme-Szenarios vorbringst, sobald ich dir eine Nicht-Cast-Lösung aufzeige. Komisch, oder?

    Ich muss ja meine Argumentation.... verteitigen.
    Ich versuche halt so zu programmieren, dass selbst die abwegigsten Ausnahme-Szenarien umgesetzt werden können.



  • frager schrieb:

    Gegen Schlüssel jeglicher Art bin ich ziemlich alergisch. Sie erfordern abwärtskompatibilität. Vor allem, wenn man Komponenten von 3. herstellern verwendet. Ich will aber nicht abwärtskompatibel sein. Will auch jederzeit die schlüssel ändern können, wenn ich lustig danach bin.

    Habe vergessen dazu zu sagen, dass ich zusätzliche Typinformationen nicht toll finde.



  • Bei Redundanz geht es mir nicht so sehr 4 bytes einzusparen, sondern um den zusätzlichen Aufwand um Sicherzustellen, dass der zeiger IMMER auf ein gültiges Objekt verweist.

    Dann nimmst du Smartpointer, wie z.B. std::tr1::shared_ptr. Hat aber nichts mit OOD zu tun, sondern mit C++-Technik. Aber es gibt zu allem saubere Lösungen. Hat aber letzendlich nichts mit Castst zutun?

    Habe vergessen dazu zu sagen, dass ich zusätzliche Typinformationen nicht toll finde.

    Ehm, und dynamic_cast benötigt keine Typinformationen? 😮 dynamic_cast arbeitet intern sicherlich mit Strings. 😉

    Naja, wenn du deine alten und (in meinen Augen) falschen Argumente _verteidigen_ mußt oder willst, breche ich hier meine Thread-Beteiligung ab. Denn das ist nicht gerade eine Basis, jemandem HELFEN zu können, der es nicht verstehen will.



  • Artchi schrieb:

    Ehm, und dynamic_cast benötigt keine Typinformationen? 😮 dynamic_cast arbeitet intern sicherlich mit Strings. 😉

    Diese Typinformationen sind schon eingebaut. Ich hoffe es würde auch keiner auf die Idee kommen type_info::name() zum verwenden um persistente daten typen zuzuordnen.

    Naja, wenn du deine alten und (in meinen Augen) falschen Argumente _verteidigen_ mußt oder willst, breche ich hier meine Thread-Beteiligung ab. Denn das ist nicht gerade eine Basis, jemandem HELFEN zu können, der es nicht verstehen will.

    Sorry, falls du mich falsch verstanden hast. Ich lasse mich gerne des besseren belehren. Bloß in meinen Augen sieht so aus, dass bei der alternative zu dynamic_cast eine unmenge an arbeit (== potentielle bugs) dahintersteckt. Dass ich durch diese zusätzlichen Aufwand etwas gewinne (vermeintlich besseres Objektorientiere-Design, nehme ich an), davon hast du mich noch nicht so recht überzeugt.



  • Zwischenmenschliche Kommunikation ist für mich eine komplizierte Sache. U.a. weil Deutsch nicht meine Muttersprache ist. Es enstehen dann immer solche "Kommukationsfehler" wo ich nicht recht weiß warum mich der andere nicht verstanden hat.

    So als anmerkung...


Anmelden zum Antworten