Implizites Teilen von Daten (in Qt)



  • Stiefel2000 schrieb:

    Die Frage ist nur, ob ich mit dem QPointer nicht das Prinzip von QSharedPointer aushebele

    Natürlich tust du das.

    Was mir noch auffällt, du hast den Zuweisungsoperator nicht überschrieben, das ist auch ein Problem.

    Und das QByteArray als Zeiger zu übergeben ist sehr unschön.



  • Das ByteArray hatte ich schon zu einer uebergebenen Referenz geaendert, QSharedData soll ja was zu tun bekommen (auch wenn ich nicht weiss, ob das irgendwo etwas ausmacht oder wirklich nur schoener ist).

    Den Zuweisungsoperator habe ich in meinem Code auch ueberschrieben. Hier mal der aktuelle Zustand:

    class ObjectPrivate : public QSharedData
    {
    public:
        ObjectPrivate() : byte(QByteArray()) {}
        ObjectPrivate(const ObjectPrivate &other) : QSharedData(other), byte(other.byte) {}
        ~ObjectPrivate() {}
    
        QByteArray byte;
    };
    
    class Object : public QObject
    {
    public:
        inline Object(QObject *parent = 0) : QObject(parent), d(new ObjectPrivate()) {}
        inline Object(const Object &other) : QObject(other.parent()), d(other.d) {}
        ~Object() {}
    
        inline QByteArray getByte() { return(d->byte); }
        inline void setByte(QByteArray &bt) { d->byte = bt; }
    
    	Object &operator=(const Object &other) {
            if(this == &other)
            {   return(*this);
            }
            d->byte = other.d->byte;
            return(*this);
        }
    private:
        QSharedDataPointer<ObjectPrivate> d;
    };
    

    Und was das Aushebeln angeht...vermutlich haette ich einfach die Finger von QSharedData lassen sollen. Es sah so aus, als wuerde es mir weiterhelfen und hat im Endeffekt den Code etwas verschoenert.
    Ich erzeuge momentan immer nur eine Kopie vom Objekt (class Object), nicht mehrere. Der Speicherplatzvorteil ist also noch da, denke ich. Allerdings gehoert diese Kopie einer ganzen Gruppe von Objekten, und beim Zerstoeren der Gruppe kann auch die Kopie auf den Muell. Da ist das Verpacken von Object in QPointer<Object> eigentlich eine ganz nette Loesung. Wenn ich das richtig sehe, teile ich die Daten erst implizit und dann ein weiteres Mal explizit, damit sollte QPointer in Ordnung gehen. Oder?

    Vielen Dank uebrigens fuer den netten und kompetenten Rat :). Leider scheint es im GUI-Forum ziemlich ruhig zu sein. Und ganz simple Anfaengerfragen stelle ich mittlerweile (hoffentlich) nicht mehr.



  • Kopier im Zuweisungsoperator den QSharedDataPointer, nicht die Daten (d = other.d).

    Ob du das shared data brauchst, musst du wissen 😉 Ein gutes Beispiel, wo man das brauchen könnte, ist ein QString oder QList. Die STL Klassen machen kein copy on write, d.h. wenn du einen std::vector per Kopie übergibst, werden die Daten kopiert. Bei den entsprechenden Qt Klassen werden die Daten erst dann kopiert, wenn sie auch wirklich verändert werden. Das ist vor allem für ständig verwendete Bibliotheksklassen sinnvoll. Ob das bei eigenen Klassen Sinn macht, muss man anhand des Zugriffsmusters entscheiden.
    Wenn das Objekt eh nicht verändert wird, brauchst du auch kein shared data.

    Was das Problem bei dir ist, seh ich grad nicht. Ich hab sowas auch schon paar mal verwendet, allerdings nicht oft. Dein Code schaut zwar noch nicht schön aus (die inlines kann man weglassen, entscheidet der Compiler eh selber, setByte sollte eine konstante und keine einfache Referenz bekommen, der Zuweisungsoperator ist wie gesagt noch nicht richtig und unschön).
    Auch wenn du jetzt in dem Fall im Endeffekt kein shared data brauchen solltest, solltest du trotzdem versuchen das Problem zu lösen, um was zu lernen.



  • Mechanics schrieb:

    Kopier im Zuweisungsoperator den QSharedDataPointer, nicht die Daten (d = other.d).

    Viel schoener, danke. 🙂

    Mechanics schrieb:

    Wenn das Objekt eh nicht verändert wird, brauchst du auch kein shared data.

    Es kann geloescht werden, von verschiedenen Kopie-Inhabern. Es ist noch ein QCache im Spiel, der das Loeschen ziemlich rabiat durchfuehrt. Nur deswegen brauchte ich ueberhaupt Kopieen, damit der Cache nicht das Programm zum Absturz bringen kann. Und dann kam eins zum anderen - aber die letztendliche Loesung gefaellt mir jetzt schon, auch wenn vermutlich hoechstens ein paar hundert KB an Speicher gespart werden.

    Mechanics schrieb:

    setByte sollte eine konstante und keine einfache Referenz bekommen, der Zuweisungsoperator ist wie gesagt noch nicht richtig und unschön

    Wenn man einfach eines Tages drauf los programmiert, mit jedem Tag etwas dazu lernt, aber eben alles nur selbst erlernt, dann sieht Code wohl so aus. Und ich moechte lieber nicht erwaehnen, wieviel schlimmer es auf dem langen Weg hierher so aussah. 😉

    Mechanics schrieb:

    Auch wenn du jetzt in dem Fall im Endeffekt kein shared data brauchen solltest, solltest du trotzdem versuchen das Problem zu lösen, um was zu lernen.

    Klar. Gelernt habe ich ja jetzt schon etwas, habe vorher selten Referenzen verwendet und noch nie mit QShared* gearbeitet. Das Problem der Abstuerze ist grossteils gewichen - und die verbleibenden Abstuerze umgehe ich jetzt mit QPointer, der an der Stelle definitiv angebracht ist.

    Also nochmals vielen Dank.



  • Stiefel2000 schrieb:

    Das Problem der Abstuerze ist grossteils gewichen - und die verbleibenden Abstuerze umgehe ich jetzt mit QPointer, der an der Stelle definitiv angebracht ist.

    Wahrscheinlich eher nicht 😉
    Mir scheint, dir ist noch nicht ganz klar, wie die Abhängigkeiten aufzubauen sind. Das ist völlig ok, wenn du noch beim Lernen bist, man lernt schließlich nie aus. Du solltest dich nur nicht mit einer Lösung, die zu funktionieren scheint, die du aber nicht ganz verstehst, zufriedengeben. Denk lieber noch ein bisschen länger über deine Probleme und deinen Aufbau nach. Wem gehören die Objekte, wer darf sie löschen, wie kriegt man mit, ob sie noch gültig sind oder nicht, muss man sie oft kopieren/verändern usw. Wenn das geklärt ist, entscheidet man sich für einen passenden Mechanismus zur Speicherverwaltung. Das können einfache Zeiger sein, shared pointer, vielleicht einfach ein QObject, oder QPointer, oder shared data... Aber man sollte sich im Klaren sein, warum man was braucht.
    Wie hast du jetzt QPointer und QSharedDataPointer zusammengebracht? Das erscheint mir jetzt nicht sinnvoll.



  • / A
    QCache-...
          \ Z
    

    Ich habe einen QCache, dessen Inhalt an anderer Stelle gebraucht wird (bei A bis Z). Da der QCache seine verwalteten Objekte jederzeit loeschen darf, erstelle ich bei Bedarf eine Kopie vom gewuenschten Objekt, mit der dann gearbeitet wird. Mit etwas Glueck bleibt das Original aber die gesamte Verwendungs-Zeit im Cache - dann waere jede einzelne Kopie Verschwendung. Implicit sharing klang nach einer guten Loesung um die Objekte im Cache und in Verwendung solange wie moeglich synchron zu halten.
    Da kam der QSharedDataPointer ins Spiel. Er schuetzt weniger vor Aenderungen als unerwartetem Loeschen durch den Cache - sofern Loeschen als Aenderung zaehlt.

    Eine erstellte Kopie eines Objektes im Cache wird nun wiederum bei A bis Z benoetigt. Da ich keine Aenderung dieser Kopie (ausser das Loeschen, dieses Mal durch A bis Z) erwarten muss, habe ich die eine Kopie jetzt allen A bis Z gleichzeitig in die Hand gedrueckt. Damit hat die Kopie keinen eindeutigen Parent, muss aber irgendwann entsorgt werden (der Cache kann dafuer ja nicht mehr sorgen, er verwaltet die Kopie nicht). Da ich weiss, dass A bis Z zur gleichen Zeit die Kopie nicht mehr brauchen, kann ich sie loeschen lassen. Aber natuerlich nur einmal, nicht Z mal - und da hilft der QPointer. Kopie wird einmalig geloescht, A bis Z sind zufrieden und koennen sich selbst zerstoeren.

    Klingt mir eigentlich ganz gut so. Auch nach dem Versuch, die Zusammenhaenge auszuformulieren.



  • Stiefel2000 schrieb:

    Aber natuerlich nur einmal, nicht Z mal - und da hilft der QPointer. ... Klingt mir eigentlich ganz gut so. Auch nach dem Versuch, die
    Zusammenhaenge auszuformulieren.

    Bis zu dem zitierten Teil hat sich das auch ganz brauchbar angehört, aber dann kam "und da hilft der QPointer" und da bin ich ausgestiegen.
    Weiß jetzt auch nicht, wie du ihn genau verwendest. Da könntest du ruhig etwas Code posten.

    Implicit sharing verwendet man meist mit Objekt-Semantik und nicht mit Zeigern. Man arbeitet z.B. meist mit einem QByteArray, nicht mit einem QByteArray*. Von daher muss eigentlich niemand irgendwelche Kopien löschen. Alle bekommen nur Objekte. Dass die Daten in Wirklichkeit nur einmal existieren, ist gekapselt. Wenn du den QCache auch kapselst, könnte man sich das irgendwie so vorstellen:

    class ObjectProvider
    {
      QCache<QString, Object> cache_;
    public:
      Object provideObject(const QString& id) {
        return *_cache[id];
      }
    };
    
    class ObjectConsumerA
    {
    public:
      void foo()
      {
        ObjectProvider * provider = getObjectProvider(); //woher auch immer, z.B. DI
        Object obj = provider->provideObject(key);
        obj.bar();
      }
    };
    

    So... Worauf ich hinaus will, die Clients bekommen die Objekte und löschen nichts. Dafür ist implicit sharing ja da. Löschen darf die nur noch der QCache.

    Wenn du irgendwann meinst, das Objekt darf man jetzt ganz sicher löschen und aus dem Cache entfernen, kannst du ja eine entsprechende Methode einbauen, die den Cache veranlasst, das Objekt löschen. Aber QSharedDataPointer kümmert sich darum, dass die eigentlichen Daten (also das QSharedData Objekt) erst dann gelöscht wird, wenn keine Referenzen darauf mehr existieren.



  • Dass ich Pointer benutzen wollte, lag daran, dass ich keine eigenen Header in eigene Header einbinden moechte. Das ganze funktioniert ebensogut, wenn ich nur Referenzen und nirgendwo Pointer verwende - ich hab's gerade probiert (und damit hat der QSharedDataPointer extremen Nutzen). Nur habe ich jetzt ein #include in einem Header, das ich gern vermieden haette. Solange ich Pointer auf die Klasse verwendet habe, die ich einbinden muss, reichte "class XYZ *". Jetzt aber sagt mir der Compiler, dass am zusaetzlichen #include kein Weg vorbei fuehrt ("field ... has incomplete type"). Ich bin jetzt nicht sicher, was das schlimmere Uebel ist.



  • Was du wo includen musste sollte nie deine Architekturüberlegungen beeinflußen. So, und wo ist jetzt das Problem, eigene Header in eigene Header einzubinden? Das macht man die ganze Zeit.



  • Mechanics schrieb:

    Was du wo includen musste sollte nie deine Architekturüberlegungen beeinflußen. So, und wo ist jetzt das Problem, eigene Header in eigene Header einzubinden? Das macht man die ganze Zeit.

    Habe ich bisher vermeiden koennen - es erhoeht den Kompilier-Aufwand. In einem meiner fruehen Projekte habe ich Header recht bedenkenlos eingebunden, und dann wurde bei jeder Aenderung irgendeiner Header-Datei im ganzen Programm alles neu kompiliert. Und damals habe ich mir angewoehnt, strikt keine eigenen Header in eigene Header einzubinden. Im momentanen Programm ist die Objekt-Hierarchie noch sehr klar, sodass die Auswirkungen minimal sind. Aber zur Regel werden darf es sicher nicht, das Kompilieren des gesamten Projektes dauert einige Minuten.

    Aber um das ganze hier mal abzuwuergen: Ich habe mich den ganzen Tag mit Referenzen und "shared data" befasst und so einiges gelernt. Ich nutze jetzt nirgendwo mehr einen Pointer auf die voluminoesen Daten, sondern nur noch "shallow copies". Fuer meine Standards ist das Ergebnis sehr zufriedenstellend 🙂 - vielen Dank fuer deine Hilfe. Morgen werde ich mir dann ein anderes Problem suchen, an dem ich mir die Zaehne ausbeissen kann.


Anmelden zum Antworten