QList::append mit komplexen Objekten

Du bist neu in der Welt von C++? Dann schau hier herein!
Antworten
Spinoza
Beiträge: 23
Registriert: 4. März 2011 01:07

QList::append mit komplexen Objekten

Beitrag von Spinoza »

Hi,
habe eine grundlegende Frage zur Benutzung von QList (und allen ähnlichen containern).
Sei MyObj eine nichttriviale Klasse mit eigenem Speicher auf dem heap, welchen sie z.B. im Konstruktor mit new reserviert und im Destruktor mit delete freigibt.
Nun zur eigentlichen Liste: QList<MyObj> myList;
Wenn ich nun zu myList etwas hinzufügen möchte, habe ich zwei Möglichkeiten: Entweder, ich erzeuge eine MyObj-Instanz auf dem stack und übergebe sie an myList.append():

Code: Alles auswählen

MyObj myobj;
myList.append(myobj);
Oder ich erzeuge die Instanz auf dem heap und übergebe myList.append() den dereferenzierten pointer:

Code: Alles auswählen

MyObj *myobj = new MyObj;
myList.append(&myobj);
In beiden Varianten kommt mir das Ganze unsauber vor. Denn append() kopiert ja, wenn ich das richtig verstanden habe, das Objekt einfach mit memcpy in seinen eigenen Speicher.
Im ersten Falle dürfte etwas schiefgehen, sobald der Kontext in dem die Instanz erzeugt wurde verlassen wird, und automatisch der Destruktor aufgerufen wird. Denn dann wird der Speicher freigegeben, auf den die kopierte MyObj-Instanz in der Liste noch zugreifen würde.
Im zweiten Fall (wenn ich das Objekt am Ende nicht mit delete lösche) bleibt zwar der dynamische Speicher in der MyObj-Instanz intakt, allerdings dürfte sich ein Speicherleck ergeben, da append() die MyObj-Instanz kopiert. Die ursprüngliche Instanz bleibt im Speicher und der Zeiger darauf geht bei Verlassen des Kontexts verloren.

Wie macht man's richtig? Oder ist QList<MyObj> nur Sinnvoll, wenn MyObj eine einfache Klasse ist, die keinen eigenen dynamischen Speicher hat? Man müsste ja folglich fast immer QList<MyObj*> verwenden, was ich etwas merkwürdig fände, angesichts der Omnipräsenz von QList<QString>, QList<QList<..> >, etc.

Das Verwenden von QList<MyObj*> Wäre jetzt kein Drama, aber gerade in Bezug auf QDataStream &operator<< und allgemeinem handling etwas unpraktischer.

Vielen Dank,
Spinoza
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Re: QList::append mit komplexen Objekten

Beitrag von franzf »

Spinoza hat geschrieben:Denn append() kopiert ja, wenn ich das richtig verstanden habe, das Objekt einfach mit memcpy in seinen eigenen Speicher.
Nein, hast du nicht richtig verstanden. Es wird das OBJEKT kopiert, dafür gibt es Kopierkonstruktor und operator=. Nach dem append gibt es ZWEI Objekte: Das eine das du erstellt hast (myObj), das andere in QList.
Probleme gibt es nur mit Membern, die im Freispeicher angelegt werden - wie in deinemBeispiel. Wenn du keinen eigenen Kopierkonstruktor/operator= definierst, wrd einfach die Adresse des Members kopiert. Der Zeiger zeigt dann AUF DAS SELBE OBJEKT im Speicher, zerstört ein Objekt den Member, zeigt der andere auf Müll!
Beispiel:

Code: Alles auswählen

class Klasse {
public:
  int* member_;  // ist hier nur zur Anschauung public, sollte natürlich in den private-Bereich
  Klasse()
   : member_(new int(10))
  {}

  ~Klasse() {
    delete member_;
  }
};

int main() {
  Klasse obj;
  { // neuer Scope
    Klasse obj2 = obj;
    assert(obj.member_ == obj2.member_);
  } // scope schließt, obj2 wird zerstört
  // obj.member_ wurde nun gelöscht, der Speicher freigegeben, Zugriffe können in einem SegFault enden!
}
Wie macht man's richtig?
Prinzipiell QList<MyObj> verwenden. Ist das einfachste und sicherste, da auch die Elemente in der Liste automatisch korrekt zerstört werden. Wenn du auf Polymorphie angewiesen bist, brauchst du Zeiger, dann kommen Basiklassenzeiger rein.
Wenn die Objekte in der Liste gültige Referenzen auf andere Objekte sein müssen, brauchst du auch Zeiger.
Spinoza
Beiträge: 23
Registriert: 4. März 2011 01:07

Beitrag von Spinoza »

Ah, danke für die Richtigstellung.
Das heisst, wenn ich meinen Klassen einen maßgeschneiderten Copyconstructor und operator= schenke, der sich auch um dynamisch reservierten Speicher kümmert, ist QList<MyObj> eine sichere angelegenheit.

Nun eine angelehnte Frage: Zeiger auf Elemente in einer QList<MyObj> sind flüchtig, oder? Wenn ich also zwei Listen habe, eine QList<MyObj> data; und eine QList<MyObj*> selection; wobei selection-Elemente auf eine gewisse Untermenge der data-liste zeigen, besteht die Gefahr, dass selection irgendwann segfaults produziert, weil data intern umgewürfelt hat?
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Das heisst, wenn ich meinen Klassen einen maßgeschneiderten Copyconstructor und operator= schenke, der sich auch um dynamisch reservierten Speicher kümmert, ist QList<MyObj> eine sichere angelegenheit.
Definitiv.
deswegen geht ja auch ein std::list<std::string> z.b. überhaupt erst.

Zeiger auf Elemente in einer QList<MyObj> sind flüchtig, oder? Wenn ich also zwei Listen habe, eine QList<MyObj> data; und eine QList<MyObj*> selection; wobei selection-Elemente auf eine gewisse Untermenge der data-liste zeigen, besteht die Gefahr, dass selection irgendwann segfaults produziert, weil data intern umgewürfelt hat?
Jein .... der Container bestimmt das, und wie QList intern funktioniert, bzw. welche garantien sie gibt, müssste man aus der Doku nehmen.

Ich nehme QList recht selten, weil das Ding iss nen zwitter zwischen Vector und Liste. Aber fuer Models in Qt ist dass ding dann doch recht praktisch.

std::list und QLinkedList sind reinrassige verkettete Listen.
Bei denen ist garantiert, das die Adresse des Objects in der Liste Konstant bleibt, bis du das Object aus der Liste rausholst, klar, oder eben die Liste loeschst.

Mitm Vector gaenge das zwar. z.b. auch, aber wenn du ein element einfuegst, kann es sein dass die adressen aller vorher eingefuegten Objecte sich aendert, wenn das Ding halt expandiert. Deswegen musst du das expandieren verhindern, eben z.b. das erst die zeiger nimmst wenn sich das ding ned aendert, oder garantiert gross genug anlegst den vector, das er nie zum expandieren kommt.

Wenn Du dann aufpasst, dass deine zuordnungscontainer vor der liste, die die Objecte haelst, loeschst, oder die nix mehr mit ihren ungültigen Zeigern dann drine was anfangen koennen, kannst du sowas wie du vorhasst, machen.

Beispiel ....

Code: Alles auswählen

std::list<MyClass> mClassList. 

mClassList.push_back(MyClass(X,Y,Z)); // Einfuegen ueber Temporaere Kopie, die der compiler hoffentlich rausoptimiert ... wenn das push_back durchlauft iss das Element auch drinne ! 

MyClass * pMyRefToMyClass = &mClassList.back(); /// Adresse des letzten, eben eingefuegten Objectes holen ... 

/// Dein Zeiger auf Object kannst dann in andere Verwaltungscontainer packen, aber wie immer im Hinterkopf, sobald das Teil aus der Liste fliegt, bzw. die liste ungueltig wird ... ist dein zeiger auch ungültig .... 
Verwende Ich btw auch ab und an, mit aehnlichen vorraussetzungen wie du . Klassen deren Kopien nicht trivial sind, und ich muss die in containern ab und an schnell umsortieren (variabler Sortier-Algo z.b.). Dann bietet sich sowas an ....

Ciao ...
Spinoza
Beiträge: 23
Registriert: 4. März 2011 01:07

Beitrag von Spinoza »

Alles klar, danke euch beiden!
Antworten