Verständnis: delete() bei dynamischen Strukturen innerhalb dynamischer Strukturen



  • Hallo,

    ich versuche grad ein Programm zu entwickeln, dass selbstständig eine vorgegebene doppelt verkette Liste nach bestimmten Kriterien durchsucht und evtl. um einige Elemente vergrößert oder auch Elemente aus der Liste entfernt.

    Da die Listenelemente (Objekte) ally dynamisch erzeugt sind, kann ich den Speicher mit delete(Object) wieder frei geben, richtig?

    (Ich kann das nicht genau sagen, da mein Win-XP-Taskmanager nicht anzeigt, dass nach dem Löschen (auch vieler) Elemente der Speicherbedarf des Programms kleiner wird - weiss leider nicht warum)

    Jetzt ist es so, dass ich nicht nur dynamisch ein Listenelement erzeuge, sondern innerhalb des Objektes ebenfalls dynamische Strukturen vorhanden sind - in meinem Fall dynamisch erzeugte Arrays.

    In etwa so:

    class A {
      int* array;
    
      A(int n) {
        array = new int[n];
      }
    };
    main() {
      A* a = new A(16);
    }
    

    Fragestellung
    Wenn ich jetzt ein Objekt der Liste löschen, also den Speicher wieder frei geben möchte, reicht dann der Aufruf

    delete(a);
    

    aus oder muss ich zusätzlich auch das Array löschen, bevor ich das Objekt lösche? Also z.B.:

    int puffer* = a->array;
    delete(puffer);
    delete(a);
    

    Wäre über Antwort sehr dankbar, da ich es wie gesagt nicht selbst bestimmen kann, da der genutzte Speicher meines Programms laut Taskmanager nicht kleiner wird, egal welche Version ich benutze.

    Vielen Dank und Gruß,
    spacemanspiff



  • spacemanspiff schrieb:

    Da die Listenelemente (Objekte) ally dynamisch erzeugt sind, kann ich den Speicher mit delete(Object) wieder frei geben, richtig?

    Nicht können sondern eher müssen 😉

    spacemanspiff schrieb:

    (Ich kann das nicht genau sagen, da mein Win-XP-Taskmanager nicht anzeigt, dass nach dem Löschen (auch vieler) Elemente der Speicherbedarf des Programms kleiner wird - weiss leider nicht warum)

    Das hat zwei gründe, zum einen setzt du delete falsch ein, zum anderen Wird der Speicher den du Freigibst nicht unbedingt von Windows auch sofort als solcher übernommen (Das Betriebssystem kann von sich aus bestimmte Strategien fahren).

    spacemanspiff schrieb:

    Jetzt ist es so, dass ich nicht nur dynamisch ein Listenelement erzeuge, sondern innerhalb des Objektes ebenfalls dynamische Strukturen vorhanden sind - in meinem Fall dynamisch erzeugte Arrays.

    Ein unschönes Konstrukt, aber möglich... nur das du sicher hier nicht echten Code Postest, da sowas wie public/private... interessant wären.

    Grundsätzlich:
    a) zu jedem new gehört ein delete
    b) zu jedem new[] gehört ein delete[]

    int* a = new int(1);
    delete a;
    int* b = new int[20];
    delete[] b;
    

    Zweitens: Wenn du Klassen schreibst sollten diese die Interna kapseln. Sprich in deinem Fall sollte das innere Array private sein, und du Zugriffoperatoren für die enthaltenen Elemente schreiben. Wenn du im Konstruktor ein Element mit new allozierst, solltes du dieses auch im Destruktor freigeben. Dann stellt sich deine Frage nämlich auch nicht, weil beim Aufruf des Destruktors eines Elementes auch seine interna aufgeräumt werden.

    Sprich:
    zu einer sauberen Klasse A die im Konstruktor alloziert und im Destruktor frei gibt, musst du dich nicht mehr um die internen Listen kümmern, sondern nur um die weiteren Allozierungen/Deallozierungen die du von außerhalb der Klasse machst...

    cu André



  • Erst mal danke für die flotte Antwort. Hättest aber ruhig auch auf meine eigentliche Fragestellung eingehen können. 🙂 🙂

    asc schrieb:

    spacemanspiff schrieb:

    (Ich kann das nicht genau sagen, da mein Win-XP-Taskmanager nicht anzeigt, dass nach dem Löschen (auch vieler) Elemente der Speicherbedarf des Programms kleiner wird - weiss leider nicht warum)

    Das hat zwei gründe, zum einen setzt du delete falsch ein, zum anderen Wird der Speicher den du Freigibst nicht unbedingt von Windows auch sofort als solcher übernommen (Das Betriebssystem kann von sich aus bestimmte Strategien fahren).

    Ok, zumindest heisst das schon mal, dass nur wenn der genutzte Speicher nicht weniger wird, das nicht auch aussagt, dass es fehlerhaft ist.

    asc schrieb:

    spacemanspiff schrieb:

    Jetzt ist es so, dass ich nicht nur dynamisch ein Listenelement erzeuge, sondern innerhalb des Objektes ebenfalls dynamische Strukturen vorhanden sind - in meinem Fall dynamisch erzeugte Arrays.

    Ein unschönes Konstrukt, aber möglich... nur das du sicher hier nicht echten Code Postest, da sowas wie public/private... interessant wären.

    Grundsätzlich:
    a) zu jedem new gehört ein delete
    b) zu jedem new[] gehört ein delete[]

    int* a = new int(1);
    delete a;
    int* b = new int[20];
    delete[] b;
    

    Zweitens: Wenn du Klassen schreibst sollten diese die Interna kapseln. Sprich in deinem Fall sollte das innere Array private sein, und du Zugriffoperatoren für die enthaltenen Elemente schreiben. Wenn du im Konstruktor ein Element mit new allozierst, solltes du dieses auch im Destruktor freigeben. Dann stellt sich deine Frage nämlich auch nicht, weil beim Aufruf des Destruktors eines Elementes auch seine interna aufgeräumt werden.

    Sprich:
    zu einer sauberen Klasse A die im Konstruktor alloziert und im Destruktor frei gibt, musst du dich nicht mehr um die internen Listen kümmern, sondern nur um die weiteren Allozierungen/Deallozierungen die du von außerhalb der Klasse machst...

    cu André

    Zur weiteren Erklärung:

    Bei meinem Programm kommt es immens auf Schnelligkeit an, daher dachte ich (kann aber als Anfänger auch falsch liegen), dass ich zugunsten der Performanz auf die Kapselung verzichten kann, um direkten Zugriff auf die Member zu haben und nicht über getter und setter gehen muss. Das sind doch zusätzliche Operationen oder nicht? 😕
    Deshalb sind meine Attribute entgegen des OOP-Paradigmas als public deklariert.

    Was mein Programm im Prinzip tut ist folgendes:

    Das Programm durchläuft dynamisch einen Binärbaum der Höhe 2^n und zwar von der Wurzel p zur tiefergelegenen Ebene p+1, zur tiefergelegenen Ebene p+2, etc. bis es bei den Blättern der Ebene n ankommt. Jede Ebene wird nach der Erzeugung einmal durchlaufen und jeder Knoten nach gewissen Kriterien untersucht und eventuell aus dem Baum entfernt, so dass sich der Suchraum verkleinert. Ist Ebene x untersucht wird mit den erhalten gebliebenen Knoten die nächsttiefere Ebene erzeugt und ebenfalls untersucht.
    Am Ende des Programms habe ich eine doppelt verkettete Liste mit allen Blättern, die meinen Kriterien entsprechen und kann dort Operationen auf den verbliebenen Objekten durchführen. Die member der Blätter sind neben ein paar Prüfwerten eben auch arrays der Grösse n. Deshalb habe ich ein dynamisches Array innerhalb der Objekte, so dass ich zu Beginn des Programms einen Wert einlesen kann, der die Tiefe des Baumes und die (gleiche) Länge des Arrays in den Blättern bestimmt.

    Damit ich sicher gehen kann, dass der Speicher nicht zu schnell belegt ist und auf den virtuellen Speicher zugegriffen wird, lautet daher die Fragestellung:

    Reicht es, das Objekt zu löschen auch wenn dynamisch erzeugte public-member enthalten sind, um den gesamten Speicher wieder freizugeben, oder muss ich die dynamischen member einzeln freigeben?

    Das ging für mich nicht deutlich genug aus Deinem Post hervor.

    Vielen Dank und Gruß,
    spacemanspiff



  • spacemanspiff schrieb:

    Erst mal danke für die flotte Antwort. Hättest aber ruhig auch auf meine eigentliche Fragestellung eingehen können. 🙂

    Das bin ich schon, nur habe ich dir keine fertige Lösung präsentiert.

    Ich Wiederhole nochmal:

    asc schrieb:

    Grundsätzlich:
    a) zu jedem new gehört ein delete
    b) zu jedem new[] gehört ein delete[]

    Sprich wenn du die arrays mit new[] anlegst sind diese auch mit delete[] zu löschen; Es reicht nicht A zu löschen, wenn sich dieses nicht im Destruktor um die Freigabe kümmert. Wie genau willst du es eigentlich noch gesagt bekommen? 😉

    spacemanspiff schrieb:

    Bei meinem Programm kommt es immens auf Schnelligkeit an, daher dachte ich (kann aber als Anfänger auch falsch liegen), dass ich zugunsten der Performanz auf die Kapselung verzichten kann, um direkten Zugriff auf die Member zu haben und nicht über getter und setter gehen muss. Das sind doch zusätzliche Operationen oder nicht? 😕

    Das ist aus mehreren Gründen unfug.

    1. Man optimiert nicht an einer Stelle Code wenn man noch nicht weiß ob diese Stelle wirklich der Flaschenhals wird (Stichwort: Profiler) - Optimieren tut man erst dann wenn es garantiert ist das es die Stelle betrifft.
    2. Wenn du schon Angst um die Geschwindigkeit einer Indirektsionsstufe hast, solltest du gleich in Assembler entwickeln. Selbst in der Spieleentwicklung wird gekapselt (und dort geht es ja auch um Berechnungsgeschwindigkeit), sonst wären alle Beispiele an die ich mich aus DirectX Büchern noch erinnern kann hinfällig.

    Wenn du schon einen wirklichen Flaschenhals entfernen willst, wirst du eher am new/delete als bei der Funktion optimieren müssen (Stichwort: replace new). Aber selbst das macht man nicht wenn man nicht 100%ige Sicherheit über die Problemstellen hat.

    Es gibt eine 90:10 Regel in der Softwareentwicklung. 90% der Zeit wird ein Programm in 10% des Codes verbringen. Und es ist wirklich unmöglich diese 10% auf reiner Schätzbasis vorher zu optimieren.

    Wenn es dir um Performance geht solltest du auch grundsätzlich die Initialisierungsliste eines Konstruktors verwenden! Das ist aber eh immer eine gute Idee.

    spacemanspiff schrieb:

    Deshalb sind meine Attribute entgegen des OOP-Paradigmas als public deklariert.

    Nichts für ungut, aber dann solltest du gleich C++ sein lassen und auf C wechseln. Irgendwo hat Bjarne Stroustrup mal gesagt das C++ so konzipiert wurde das die Performance mindestens bei 90% von C liegen sollte, Exceptions reduzieren diese Garantie nochmals um 10%.

    Aber: Die Performance ist zumeist garnicht das Problem. Es geht auch um Wartbarkeit. Wenn Programme wirklich sauber geschrieben sind, bekommt man nicht nur eine Reduzierung der Fehler hin, sondern du wirst dich vielleicht wundern: auch eine bessere Performance. Den bei ein gut designten Code sieht man Fehler und Probleme die man in den ach so "hochoptimierten" Code leicht übersieht.

    ==> Also: Immer erst eine saubere Entwicklung, und später dann die Problemstellen optimieren - NICHT VORHER.

    Was man natürlich grundsätzlich vorher machen sollte ist aber Codeteile zu vermeiden die zwingend langsamer sind, und sonst keinen Programmtechnischen Unterschied machen:
    1. wenn es keinen Unterschied in der Lesbarkeit gibt sollte man ++i statt i++ verwenden.
    2. Integrale Datentypen (int, float...) per Wert an Methoden übergeben, Objekte per konstanter Referenz.
    3. Verwendung der Initialisierungsliste bei Klassen
    4. Verwende STL-Container (In der Regel sind eigene Containerklassen niemals so optimiert geschrieben wie die Klassen der STL)

    spacemanspiff schrieb:

    Reicht es, das Objekt zu löschen auch wenn dynamisch erzeugte public-member enthalten sind, um den gesamten Speicher wieder freizugeben, oder muss ich die dynamischen member einzeln freigeben?

    Das ging für mich nicht deutlich genug aus Deinem Post hervor.

    Soll ich das oben stehende nochmal wiederholen?
    Wann immer du new einsetzt musst du auch delete einsetzen. Die sind immer paarweise zu verwenden. Sprich wenn du array per new allozierst reicht es nicht ein alloziertes A zu löschen, sondern das array muss auch ein delete bekommen. Und zwar nicht so wie du geschrieben hast "delete(array)", sondern mit "delete[] array;" da es auch mit new [] angelegt wurde!

    Die übliche Syntax für delete ist auch nicht das man () verwendet. Schau dir nochmal mein Beispiel aus dem ersten Post an.

    cu André


Log in to reply