Mitteilung an eine Klasse



  • Hi,

    ich habe eine Klasse namens Bild und eine Klasse namens BildData. Die Klasse Bild dient dazu, ein Bild anzuzeigen, BildData lädt/speichert Bilder und manipuliert diese. Aber was soll die Klasse Bild anzeigen? Die Daten aus der Klasse BildData. Also macht es Sinn, dass Bild von BildData erbt, das klappt auch super.

    Nun zu meinem Problem:
    Angenommen, ich will das selbe Bild an 10 verschiedenen Stellen anzeigen lassen.
    Aber warum sollte das gleiche Bild 10 mal in den Arbeitsspeicher geladen werden, wenn es doch nur einmal geladen werden muss? Also trenne ich die beiden Klassen und schreibe eine Methode, die der Klasse Bild die zugehörige BildData übermittelt. Das würde dann so aussehen:

    BildData* data = new BildData;
    data.load("bild.format");
    
    Bild bilder[10];
    
    for (int i = 0; i < 10; ++i) {
        bilder[i].setData(data);
    }
    
    // Bilder verschieben etc...
    

    Wenn man BildData löscht hat das aber fatale Folgen: Bild greift auf einen nicht mehr gültigen Pointer zu. Das führt zu 99% zum Crash.

    Und nun meine Frage:
    Wie teile ich in diesem Fall am besten der Klasse Bild mit, dass ihre zugehörige BildData gelöscht wurde?

    Danke fürs lesen!



  • Die beste Möglichkeit wäre es wohl, nicht mit nackten Zeigern zu arbeiten, sondern mit Smart-Pointern. Für deinen Zweck ist ein Zeiger mit Referenzzählung (boost::shared_ptr bzw. std::tr1::shared_ptr) besser geeignet.

    PS: Die Vererbungsbeziehung solltest du etwas überdenken - zwischen BildData und Bild existiert eine "hat-ein"-BEziehung und die drückt man besser durch Komposition aus.



  • In BildData legst du einfach eine Liste<Bild> an. Im destructor rufst du einfach von jedem BildBenutzter bildDataHasBeenRemoved() oder so auf... nicht ?

    CStoll schrieb:

    PS: Die Vererbungsbeziehung solltest du etwas überdenken - zwischen BildData und Bild existiert eine "hat-ein"-BEziehung und die drückt man besser durch Komposition aus.

    Komposition? Hä?



  • lk schrieb:

    CStoll schrieb:

    PS: Die Vererbungsbeziehung solltest du etwas überdenken - zwischen BildData und Bild existiert eine "hat-ein"-BEziehung und die drückt man besser durch Komposition aus.

    Komposition? Hä?

    Ja, Komponsition



  • Dann werde ich jetzt nochmal neu überlegen wer was erbt, danke. Ich verstehe aber noch nicht so ganz, was ein shared_ptr hier bringen soll. shared_ptr löscht, wenn er gelöscht oder resetet wurde, sein Zielobjekt. Was soll das hier bringen?

    @lk: Das geht, klar, aber dann kommt es doch wieder auf die Zuverlässigkeit des Programmierers an.



  • Razoron schrieb:

    Dann werde ich jetzt nochmal neu überlegen wer was erbt, danke. Ich verstehe aber noch nicht so ganz, was ein shared_ptr hier bringen soll. shared_ptr löscht, wenn er gelöscht oder resetet wurde, sein Zielobjekt. Was soll das hier bringen?

    shared_ptr zählt mit, wieviele Zeiger gerade auf dieses Objekt zeigen - und löscht es erst wenn der letzte Verwender nicht mehr darauf zugreift. Wenn es noch andere Verwender gibt, dekrementiert er nur einen Zähler.



  • Also meinst du, dass ich nur noch mit shared_ptr arbeiten solle? Aber das währe doch dann trotzdem nicht sicher:

    boost::shared_ptr<BildData> data(new BildData);
    data->load("bild.format");
    
    Bild bilder[10];
    
    for (int i = 0; i < 10; ++i) {
        bilder[i].setData(data);
    }
    
    // Bilder verschieben etc...
    
    delete data->get();
    

    Das würde doch trotzdem zum Crash führen, da der von shared_ptr gehaltene Pointer ungültig wäre. Gibt es nicht eine idiotensichere Version? 😃



  • Razoron schrieb:

    Also meinst du, dass ich nur noch mit shared_ptr arbeiten solle?

    Nicht unbedingt, es gibt noch ein paar andere Smart-Pointer-Klassen für verschiedene Anwendungsfälle.

    Aber das währe doch dann trotzdem nicht sicher:

    boost::shared_ptr<BildData> data(new BildData);
    data->load("bild.format");
    
    Bild bilder[10];
    
    for (int i = 0; i < 10; ++i) {
        bilder[i].setData(data);
    }
    
    // Bilder verschieben etc...
    
    delete data->get();
    

    Das würde doch trotzdem zum Crash führen, da der von shared_ptr gehaltene Pointer ungültig wäre. Gibt es nicht eine idiotensichere Version? 😃

    Wenn du mit einem Smart-Pointer arbeitest, brauchst du dich nicht mehr manuell um die Freagabe der Daten kümmern. Du legst nur den shared_ptr an, übernimmst ihn in deine Bilder und wenn alles aus dem Scope gefallen ist, werden die BildDaten automatisch wieder gelöscht:

    class Bild
    {
    public:
      ...
      void setData(const shared_ptr<BildData>& data)
      { m_data = data; }
      ...
    private:
      shared_ptr<BildData> m_data;
      ...
    };
    


  • Okay, wenn ich shared_ptr verwende, werden die Zielobjekte erst dann gelöscht, wann kein shared_ptr mehr existiert, der auf das Zielobjekt zeigt. Aber wie kann ich dem delete-Operator den Vortritt geben? Also wenn ich das Zielobjekt eines shared_ptrs lösche, wird es auch augenblicklich gelöscht und muss nicht auf andere shared_ptrs warten. Jedoch bekommen alle shared_ptrs, die noch auf das Objekt zeigen eine Nachricht oder Ähnliches.



  • Nein, nicht wirklich. Du darfst das Objekt nicht manuell löschen, solange es von einem der shared_ptr verwendet wird. Das reicht normalerweise aus, um zu verhindern, daß deine Bild-Objekte mit ungültigen Daten hantieren.

    Wenn du wirklich alle angeschlossenen Bild-Objekte benachrichtigen willst, wenn sich irgendwas an dem BildData ändert, mußt du vermutlich mehr Aufwand betreiben. Da hilft vermutlich so etwas.



  • Das könnte man dann mithilfe von Signals: http://www.boost.org/doc/libs/1_46_1/doc/html/signals.html realisieren, oder? Wenn BildData gelöscht wurde gibt es ein Signal aus, auf das dann das passende Bild reagiert.



  • Ja, signals eignen sich zur Umsetzung dieses Patterns hervorragend.



  • Okay, danke dann mach ich mich mal ran. Wenn weitere Fragen auftauchen poste ich hier nochmal und auch dann, wenn ichs geschafft habe.



  • Eisflamme schrieb:

    Ja, signals eignen sich zur Umsetzung dieses Patterns hervorragend.

    Da melde ich mal Zweifel an.



  • volkard schrieb:

    Eisflamme schrieb:

    Ja, signals eignen sich zur Umsetzung dieses Patterns hervorragend.

    Da melde ich mal Zweifel an.

    Und welche Alternative gäbe es da?



  • Razoron schrieb:

    Und welche Alternative gäbe es da?

    Weiß nicht. Um genau zu sein, habe ich keine Ahnung, warum man vorzeitig Löschen will. Wenn ein Fremdling noch einen smart pointer auf das Objekt hält, dann hat der auch seinen Grund dafür.
    Aber will man, daß jeder das Objekt löschen kann, gibt ihm doch ne lösch-Methode.
    Oder mach Zeiger auf smartpointers, dann jeder auf DEN smartpointer zugreigen und ihn releasen.



  • Könnte man auch über weak_ptr lösen, so wie ich das verstanden habe. 😉



  • Ich hab nur gesagt, Observer-Pattern lässt sich mit signals gut umsetzen. Ob seine Methode an sich Objekte vorher löschen zu wollen angebracht ist, ist eine andere Frage. 🙂



  • Eisflamme schrieb:

    Ich hab nur gesagt, Observer-Pattern lässt sich mit signals gut umsetzen. Ob seine Methode an sich Objekte vorher löschen zu wollen angebracht ist, ist eine andere Frage. 🙂

    Ah, ok. Hab das "Observer" total überlesen und dachte, es sei ein konkreter Tip für diese Bitmaps.



  • Irgendwie Zweifel ich ja die ganze Konstruktion etwas an. Für ein Bild sollte so etwas essentielles wie seine Bilddaten direkt bei der Konstruktion mitgegeben werden. Genauso wie man einen Konstruktor machen sollte das man BildData direkt so erstellen muss:

    BildData* data = new BildData("bild.format");
    

    Wenn Bild BildData anlegt, kann es sich entscheiden wann es das nicht mehr braucht und direkt wieder freigeben. RAII is ne tolle Sache.

    Aber prinzipiell ist der Typ mit den smart pointern wohl auch ne Lösung, je nachdem was du machen willst vielleicht sogar die Bessere...



  • Hi,

    ich habe eine Klasse namens Bild und eine Klasse namens BildData. Die Klasse Bild dient dazu, ein Bild anzuzeigen, BildData lädt/speichert Bilder und manipuliert diese. Aber was soll die Klasse Bild anzeigen? Die Daten aus der Klasse BildData. Also macht es Sinn, dass Bild von BildData erbt, das klappt auch super.

    Wenn die Klasse "Bild" ein Bild anzeigen soll, dann wäre als Name besser "BildAnzeiger" geeignet. Die Klasse BildData sollte in zwei Klassen zerlegt werden: BildLeserSchreiber und Bild. Manipulatoren sollten eigene Klassen sein, die mit Bildern arbeiten. Und - aber das ist jetzt mein persönlicher Geschmack - liest sich der Code mit englischsprachigen Bezeichnern besser.

    Ergebnis wäre etwas in der Art:

    class ImageViewer
    {
    public:
    ...
        void displayImage(Image& img);
    ...
    };
    
    class ImageReaderWriter
    {
    public:
    ...
        void writeImage(Image& img, Destination& dest);
        Image readImage(Source& src);
    ...
    };
    
    class UnaryManipulator
    {
    public:
        virtual void operator()(Image& src) = 0;
    };
    

    Es ist dann zu überlegen, ob Dein Image immer denselben Datentyp (z.B. unsigned char) verarbeitet, oder ob verschiedene vorkommen (RGB) können. Dann wäre es wahrscheinlich sinnvoll, Image als Template-Klasse zu entwerfen.

    Als Idee kannst Du mal hier: http://ltilib.sourceforge.net/doc/homepage/index.shtml nachschauen


Anmelden zum Antworten