Vector leeren



  • Hallo,
    ich habe einen std::vector der mit Objekten gefüllt wird (der code ist beispielhaft, also nicht auf die Benennungen achten und nicht wundern wenn ich dem vecor keinen Typ mitgebe):

    vec.push_back( new class() );
    

    Nun will ich den vector leeren. Der Vector hat die Funktion .clear. Da ich aber Objekte mit new angelegt habe, muss ich bei diesen ein delete aufrufen (oder macht das die .clear Methode selber?).
    Deshalb dachte ich mir, ich bau mir die Methode so:

    void clearVector(std::vector vec)
    {
    	for(std::vector::const_iterator iter = vec.begin(); iter != vec.end(); ++iter)
    	{
    		delete  (*iter);
    		vec.erase(iter);
    	}
    }
    

    Dies wirf immer eine Fehlermeldung. Ich denke das liegt daran, dass ich aus dem vector Elemente lösche während ich durch iteriere (dachte irgend wo gelesen zu haben, dass dies aber geht, da der Iterator eine Kopie ist).

    Wie setze ich dies am geschicktesten um?

    Eine zweite Methode soll einen vector komplett löschen. Mein Überlegung ging schief:

    void deleteVector(std::vector vec)
    {
    	for(std::vector::const_iterator iter = vec.begin(); iter != vec.end(); ++iter)
    	{
    		delete  (*iter);
    	}
    
    	delete vec;
    }
    

    Wie mache ich das am besten?

    Ich habe nun länger in google danach gesucht und versuche grad mit try and error das umzusetzen. Da ich mir aber nie sicher bin, wie es am geschicktesten ist und ob alle Objekte danach wirklich weg sind (habe bis jetzt mit Java programmiert, weshalb mir das Wissen fehlt), wollte ich zur sicherheit noch erfahrene Programmierer fragen. Ich will ja auch effizientes programmieren unter C++ lernen.

    Danke



  • Xenya schrieb:

    Dies wirf immer eine Fehlermeldung.

    Eine sehr aussagekräftige Beschreibung.

    Warum nicht so:

    void clearVector(std::vector vec)
    {
    	for(std::vector::const_iterator iter = vec.begin(); iter != vec.end(); ++iter)
    	{
    		delete  (*iter);
    	}
        vec.clear();
    }
    

    Oder aber so:

    std::vector<boost::shared_ptr<MyType> > vec;
    

    Dann musst du dir keine Gedanken um einen Löschfunktion machen.



  • Xenya schrieb:

    Da ich aber Objekte mit new angelegt habe, muss ich bei diesen ein delete aufrufen (oder macht das die .clear Methode selber?).

    Nein macht sie nicht, du musst also delete selber aufrufen.

    Allerdings: Musst du die Elemente unbedingt mit new auf dem heap anlegen? Wenn sie nicht zu teuer zu kopieren sind, kannst du sie durchaus auch auf dem Stack anlegen, das erspart dir die Speicherverwaltung.
    Alternativ gibts z.B. in boost Spezielle Pointer-Container, die auch das delete aufrufen.

    Dies wirf immer eine Fehlermeldung. Ich denke das liegt daran, dass ich aus dem vector Elemente lösche während ich durch iteriere (dachte irgend wo gelesen zu haben, dass dies aber geht, da der Iterator eine Kopie ist).

    Das Problem ist, dass beim erase alle Elemente dahinter eins nach vorne aufrücken, da es in einem vector keine Lücken gibt. Du löscht also das erste Elemen, das zweite rückt auf, und dein iterator springt eins weiter. dann löscht du das nächste Element (ursprünglich das dritte), alle anderen rücken auf...
    Abgesehn davon, dass du durch die ganze kopiererei unnötig Zeit verschwendest, löscht du nur die Hälfte aller Elemente. Und wenn du ans Ende des vectors kommt, kanns sein dass durch das erase dir das Ende unter deinem Iterator "durchrutscht" (so wie Element 2, 4 usw), und wenn du dann nochmal erhöhst kommt das Chaos.
    Zuletzt kanns auch sein, dass erase auf einen const_iterator nicht möglich ist (hab die Fehlermeldung jetzt nicht gesehen...)

    Besser erst die ganzen deletes, dann in einem Rutsch den Vector leeren:

    for (std::vector::iterator it = vec.begin(); it != vec.end(); ++it)
    {
      delete *it;
    }
    vec.clear();
    // Alternativ um den Speicher freizugeben:
    // std::vector().swap(vec);
    


  • vielen lieben Dank für die Antworten.

    Auf boost will ich gern verzichten, da dies nicht unbedingt auf dem Zieldevice später vorhanden ist.

    for (std::vector::iterator it = vec.begin(); it != vec.end(); ++it)
    {
      delete *it;
    }
    vec.clear();
    // Alternativ um den Speicher freizugeben:
    // std::vector().swap(vec);
    

    das funktioniert perfekt.

    wegen dem, dass ich das new weglassen soll.
    Ich bin in C++ nicht wirklich erfahren, deshalb könnte das nun ziemlich falsch sein:
    Wenn ich es auf dem Stack anlege verliert es doch nach dem Scope seine Gültigkeit. Deshalb dachte ich, wenn ich ohne new das Element dem Vektor hinzufüge, ist es außerhalb der Methode nicht mehr vorhanden.

    Ansonsten ist das Objekt billig zu kopieren. Kein einziges new kommt vor. Es sind nur paar Primitive und Pointer (Pointer ist ja im prinzip auch nur ein primitiver Datentyp).



  • Sofern dein Compiler schon C++0x features unterstützt gibt es dort den std::tr1::shared_ptr<>



  • Xenya schrieb:

    Auf boost will ich gern verzichten, da dies nicht unbedingt auf dem Zieldevice später vorhanden ist.

    Wenn du schon "Zieldevice" sagst, wäre ich gerade mit so vielen new/delete vorsichtig (Hört sich für mich nach ein Embedded System an). Die Container der Standardbibliothek arbeiten intern selbst mit new/delete, und die Objekte werden beim einfügen kopiert.

    In sofern würde ich, wenn kein anderer Grund wie Polymorphie oder sehr teuer zu kopierende Objekte für Zeiger spricht, wirklich mit dem Objekt arbeiten. Ja, dein lokales Objekt wird mit verlassen des Scoped gelöscht, der vector hält aber wie gesagt eine Kopie.



  • HighLigerBiMBam schrieb:

    Sofern dein Compiler schon C++0x features unterstützt gibt es dort den std::tr1::shared_ptr<>

    Falls, wie ich aus dem letzten Kommentar von Xenya herleite, ein Embedded Gerät gemeint ist, wäre das nicht nur die sprichwörtliche Kanonenkugel für die Spatzenjagd.



  • Xenya schrieb:

    ich habe einen std::vector der mit Objekten gefüllt wird (der code ist beispielhaft, also nicht auf die Benennungen achten und nicht wundern wenn ich dem vecor keinen Typ mitgebe):

    vec.push_back( new class() );
    

    Stell Dir die Objekte, die Du mit new anlegst als Häuser vor. Jedes Haus hat eine Adresse (Straße&Hausnr). Stell dir den Vector als Notizbuch vor, wo auf den ersten Seiten jeweils eine Adresse eines Hauses steht.

    Xenya schrieb:

    Nun will ich den vector leeren. Der Vector hat die Funktion .clear. Da ich aber Objekte mit new angelegt habe, muss ich bei diesen ein delete aufrufen (oder macht das die .clear Methode selber?).

    Wenn Du clear auf dem Vektor aufrufst, werden die Adressen aus dem Notizbuch wegradiert. Die Häuse gibt es dann immer noch. Du hast dann nur all die Adressen verloren.

    Xenya schrieb:

    Deshalb dachte ich mir, ich bau mir die Methode so:
    [...]
    Dies wirf immer eine Fehlermeldung. Ich denke das liegt daran, dass ich aus dem vector Elemente lösche während ich durch iteriere

    Richtig.

    Xenya schrieb:

    Wie setze ich dies am geschicktesten um?

    Spar die das erase in der Schleife und ruf nach der Schleife einfach clear auf.

    Xenya schrieb:

    Eine zweite Methode soll einen vector komplett löschen.

    Der Vektor wird durch clear schon komplett gelöscht. Er hält ja nur die Adressen der Häuser.

    Xenya schrieb:

    Mein Überlegung ging schief:

    delete vec;
    

    Wie mache ich das am besten?

    Nachdenken. Dir überlegen, was der vector wirklich speichern soll. Nur die Adresssen oder die "Häuser" selbst? Hat beides Vor- und Nachteile. Prinzipiell solltest Du Dir über die "Besitzverhältnisse" Gedanken machen. Mit Besitz meine ich hier die Verantwortung für die Verwaltung und das Löschen. C++ erlaubt es Dir, diese Verantwortungen an andere Objekte zu deligieren. Je häufiger und früher Du so etwas machst, desto weniger Gedanken musst Du dir darum machen, ob es auch wirklich keine Resourcen-Lecks gibt. In den meisten Fällen (zumindest bei dem, was ich so mache), ergibt sich ein Wald, d.h. genau ein Objekt verwaltet ein bis mehrere andere und die Wurzeln der Bäume liegen alle im automatischen Speicher. Manchmal ist auch ein "geteilter Besitz" nützlich, bei dem ein Objekt von mehreren anderen verwaltet wird. Das kann man über boost::shared_ptr oder std::tr1::shared_ptr oder std::shared_ptr erledigen. Das kommt sicherlich auch auf Deine Anwendung an, ob Du so etwas brauchst oder nicht. Diese Denkweise ist vielleicht anfangs anstrengend oder ungewöhnlich, aber nach der Zeit kommt so etwas ganz automatisch.

    In Deinem Fall hast Du den per new erzeugten Objekten keinem Besitzer zugeordnet. Deswegen musst Du sie manuell per delete löschen. Das ist suboptimal. Alternativ kannst Du auch einen boost::ptr_vector verwenden. Der speichert zwar auch nur Zeiger (was ggf ein Vorteil ist), fühlt sich aber "verantwortlich" für die referenzierten Objekte. std::vector<boost::shared_ptr<Haus> > und std::<Haus> wären andere Alternativen. Such Dir das aus, was für Deinen Fall am besten passt.

    Xenya schrieb:

    Ich habe nun länger in google danach gesucht und versuche grad mit try and error das umzusetzen.

    trial and error ist bei C++ eine schlechte Idee.

    kk



  • Warum wird hier eigentlich überall auf shared_ptr verwiesen? In den meisten fällen reicht ein unique_ptr vollkommen aus, und den gibts in boost und C++0x.



  • 314159265358979 schrieb:

    Warum wird hier eigentlich überall auf shared_ptr verwiesen? In den meisten fällen reicht ein unique_ptr vollkommen aus, und den gibts in boost und C++0x.

    Weil shared_ptr mit Container einfacher zu verwenden sind. Hirn aus beim Programmieren gogo -> 🙄

    Bei auto_ptr kommt dir das Kopierverhalten in die Quere und bei unique_ptr muss man sich mit movesemantic auseinandersetzen.



  • Man muss sich bei jedem Smartpointer mit seiner Funktionsweise vertraut machen, und eine Erklärung ala 'Nimm std::move, wenn du die in Containern speichern willst' reicht fürs erste auch aus.



  • 314159265358979 schrieb:

    In den meisten fällen reicht ein unique_ptr vollkommen aus, und den gibts in boost und C++0x.

    boost::interprocess::unique_ptr ist ohne Rvalue-Referenzen (C++0x Feature) und ohne STL-Unterstützung von move-only Typen (C++0x Feature) nur eingeschränkt brauchbar. Das letzte mal, als ich libstdc++ bzgl unique_ptr-Implementierung getestet hatte, funktionierte map<string,unique_ptr<int> > noch nicht richtig, weil es da irgendwelche Probleme mit std::pair und Move-Konstruktoren gab.

    Statt vector<unique_ptr<T> > kann man auch boost::ptr_vector<T> verwenden. Das funktioniert dann auch ohne C++0x Features.



  • Xenya schrieb:

    wegen dem, dass ich das new weglassen soll.
    Ich bin in C++ nicht wirklich erfahren, deshalb könnte das nun ziemlich falsch sein:
    Wenn ich es auf dem Stack anlege verliert es doch nach dem Scope seine Gültigkeit. Deshalb dachte ich, wenn ich ohne new das Element dem Vektor hinzufüge, ist es außerhalb der Methode nicht mehr vorhanden.

    Ja das ist falsch. Beim Hinzufügen in den vector wird nicht das Objekt selbst hinzugefügt, sondern eine Kopie erzeugt. Das Objekt wird am Ende der Methode zerstört, aber die Kopie lebt weiter, um deren Lebenszeit kümmert sich der vector.
    Dabei ist es übrigens egal, ob der vector Zeigertypen enthält. Dann werden eben Kopien der Zeiger angelegt (aber eben nicht der gezeigten Objekte).

    Also: ja, lass das new weg.



  • 314159265358979 schrieb:

    Warum wird hier eigentlich überall auf shared_ptr verwiesen? In den meisten fällen reicht ein unique_ptr vollkommen aus, und den gibts in boost und C++0x.

    Weil einige leider nicht den Luxus haben, boost und/oder C++0x zur Verfügung zu haben! 😞



  • HighLigerBiMBam schrieb:

    Sofern dein Compiler schon C++0x features unterstützt gibt es dort den std::tr1::shared_ptr<>

    Der Post war es, der mich besonders gestört hat. Denn wer C++0x hat, der hat auch nen unique_ptr.



  • 314159265358979 schrieb:

    HighLigerBiMBam schrieb:

    Sofern dein Compiler schon C++0x features unterstützt gibt es dort den std::tr1::shared_ptr<>

    Der Post war es, der mich besonders gestört hat. Denn wer C++0x hat, der hat auch nen unique_ptr.

    Abgesehen davon: Was hat denn der shared_ptr aus dem bereits existierenden TR1 mit dem zukünftigen Standard C++0x zu tun? Oder ist der TR1 kein "echter" Bestandteil des aktuellen C++-Standards?



  • TR1 ist eben Technical Report 1. Das ist der wahrscheinlich-bald-Standard, aber solange nix abgesegnet ist, kann der noch geändert werden.



  • Oh. 😮

    Theoretisch könnten dann beim nächsten C++-Standard TR1-Bestandteile geändert werden und bisheriger Code, der TR1-Futures benutzt, könnte dann nicht mehr standardkonform sein?

    Ich dachte (das ist der Fehler! ;)), dass die TR1-Inhalte quasi feste Bestandteile des folgenden Standards sind. Nach dem Motto: "Das ist garantiert im nächsten Standard dabei!"



  • Roger Wilco schrieb:

    Ich dachte (das ist der Fehler! ;)), dass die TR1-Inhalte quasi feste Bestandteile des folgenden Standards sind. Nach dem Motto: "Das ist garantiert im nächsten Standard dabei!"

    Es ist nicht garantiert dabei, aber große Teile werden übernommen, ggf. mit leichte Modifikationen, die die neuen Möglichkeiten von C++0x berücksichtigen. Danach sind 4 verschiedene Dinge zu unterscheiden:

    - Die Spezifikationen im TR1
    - Die TR1-Implementierung durch die Compilerhersteller
    - Die Spezifikation im 0x-Standard
    - Die 0x-Implementierung durch die Compilerhersteller

    Alle zusammen müssen nicht identisch sein, sollten aber nur geringe Abweichungen voneinander haben.



  • vielen lieben Dank, dass ihr euch Zeit genommen und mir nicht nur eine Lösung gezeigt, sondern auch einiges erklärt habt.

    Wenn ich es also den Vector ohne new befülle, reicht es um die Member-Variable wieder zu leeren, in dem ich nur die Swap Methode aufrufe?

    Und nen Destruktor brauche ich dafür dann auch nicht mehr, da der komplette Speicher selbst freigegeben wird, wenn das Klassen-Objekt das diese Membervariable hat, zerstört wird.



  • Swap? theVector.clear() reicht.

    Edit: Wobei std::vector().swap(vec); auch interessant aussieht, aber welche Vorteile hat das?


Anmelden zum Antworten