C++ delete Objekt, Werte bleiben erhalten?

DaysShadow

Admiral
Registriert
Jan. 2009
Beiträge
8.947
Hi,

um mich ein wenig in C++ einzuarbeiten hatte ich mir vorgenommen eine LinkedList zu schreiben, funktioniert so weit auch wie es soll.

Ich habe drei Möglichkeiten um etwas der Liste hinzuzufügen, AddFirst, AddLast und Insert an einem gegebenen index.

In meinem Testprogramm habe ich noch eine Klasse Vector2f geschrieben.
Davon erstelle ich 5 Objekte und füge sie dann der Liste hinzu, ich kann also sowohl von außerhalb der Liste als auch über die Liste auf diese zugreifen.

Füge ich alle Objekte per AddFirst oder AddLast hinzu, rufe Clear auf um die Liste zu leeren und überprüfe dann ob die Vektoren gelöscht wurden indem ich x oder y aufrufe, dann sehe dass x und y nicht mehr die Werte haben die sie haben sollten, also wurden sie gelöscht.

Füge ich alle Objekte per Insert hinzu und füge 2 der 5 Objekte zwischen die anderen, fahre dann fort wie eben, dann kann ich die Werte der zwei zwischen die anderen hinzugefügten Vektoren immer noch auslesen bzw. haben sie immer noch die richtigen Werte.

Die Frage die ich mir jetzt stelle ist, ob ich irgendwo bzw. in der Insert Methode einen Fehler gemacht habe oder nicht.
Das Problem dabei ist, dass der Destruktor aller 5 Vektoren von Clear aufgerufen wird und die Adressen derer mit denen stimmen die ich über meine außerhalb der Liste gelegenen Variablen abrufen kann.

Sollte ich mich irgendwie unklar ausdrücken, fragt einfach nach...^^

Zum Verständnis noch der Testprogrammcode:
Code:
#include <iostream>
#include <string>
#include "include/Vector2.hpp"
#include "include/LinkedList.hpp"

int main(int argc, char** argv)
{   
    // Create 5 vectors
    
    ds::utl::Vector2f* vector1 = new ds::utl::Vector2f( 50, 50 );
    ds::utl::Vector2f* vector2 = new ds::utl::Vector2f( 100, 100 );
    ds::utl::Vector2f* vector3 = new ds::utl::Vector2f( 150, 150 );
    ds::utl::Vector2f* vector4 = new ds::utl::Vector2f( 200, 200 );
    ds::utl::Vector2f* vector5 = new ds::utl::Vector2f( 250, 250 );
    
    // Create a linked list to store vectors
    
    LinkedList< ds::utl::Vector2f >* list = new LinkedList< ds::utl::Vector2f >( );
    
    // Insert them
    
    list->Insert( vector1, 0 );
    list->Insert( vector2, 1 );
    list->Insert( vector3, 2 );
    list->Insert( vector4, 2 );
    list->Insert( vector5, 2 );
    
    // Iterate through the list and print the values of the vectors
    
    for( ds::utl::Vector2f* vector; list->Next(); )
    {
        vector = list->IteratorValue();
        
        std::cout << "vector: " << vector->getX() << " | " << vector->getY( ) << std::endl;
    }

    // looks like this:
    // 50 | 50
    // 100 | 100
    // 250 | 250
    // 200 | 200
    // 150 | 150

    // Clear the list
    
    list->Clear( );
      
    // Check if everything got deleted
    
    std::cout << vector1->getX() << " | " << vector1->getY() << std::endl;
    std::cout << vector2->getX() << " | " << vector2->getY() << std::endl;
    std::cout << vector3->getX() << " | " << vector3->getY() << std::endl;

    // Values are readable 200 | 200
    std::cout << vector4->getX() << " | " << vector4->getY() << std::endl;

    // Values are readable 250 | 250
    std::cout << vector5->getX() << " | " << vector5->getY() << std::endl;
        
    std::string input;
    std::cin >> input;

    return 0;
}

Sollte es nötig sein, kann ich auch den Code zur LinkedList usw. posten, allerdings ist das dann ein wenig mehr Code :p
 
Ohne jetzt alles durchgelese zu haben: delete löscht NICHT den Speicher, sondern gibt nur den per new reservierten Speicher wieder frei.
 
Ja ok, es wundert mich halt nur trotzdem, dass nur bei Insert die Werte der beiden Objekte erhalten bleiben und sonst werden sie ja freigegeben bzw. die Werte sind futsch.
 
so wie es momentan implementiert ist, werden die vectorXY objekte nicht bei aufruf von list->Clear( ); gelöscht. das aus dem grund, weil der ansatz dazu gedacht ist, mit den selben vector-objecten auf mehreren listen zu arbeiten. müsstest deine vector-objekte also explizit löschen müssen.

kenne mich mit ds::utl:: nicht aus, falls du aber mit deinem code sowas ähnliches wie hier unten machen kannst, wird der destructor aller objekte nach verlassen der methode aufgerufen.

Code:
typedef std::pair<float, float> vec2f;
std::vector<vec2f> vec2farray;
	
vec2farray.push_back(vec2f(3.0f, 3.0f));
vec2farray.push_back(vec2f(4.0f, 4.0f));
vec2farray.push_back(vec2f(5.0f, 5.0f));
vec2farray.push_back(vec2f(6.0f, 6.0f));
 
Ich habe zwei Clearbefehle:

Clear löscht die Links als auch den Wert des Links, Clear ruft also den Destruktor des Links auf und dieser wiederum den des Werts, also in dem Falle von Vector2f.

ClearLinks löscht nur die Links und erhält den Wert des Links bzw. setze ich dabei den Zeiger des Werts auf 0 bevor ich den Link delete.

In dem Beispiel sollen aber alle Werte gelöscht werden.
 
DaysShadow schrieb:
Ich habe zwei Clearbefehle:

Clear löscht die Links als auch den Wert des Links, Clear ruft also den Destruktor des Links auf und dieser wiederum den des Werts, also in dem Falle von Vector2f.


Wie hast du das implementiert? Hoffentlich nicht, indem du wirklich direkt den Destruktor des referenzierten Objekts aufrufst?


Davon mal abgesehen, wenn du in C++ mit delete ein Objekt löschst und anschließend in den Speicherbereich langst, in dem das Objekt lag, kann es durchaus passieren, daß da noch immer die selben Werte liegen wie vor dem delete-Aufruf (solltest du aber niemals machen!!). Operator delete gibt den Speicher nur frei ... die einzelnen Speicherzellen werden nicht notwendigerweise überschrieben.
 
Also ich lösche den Link als auch den Wert mit delete, anders geht es doch nicht, oder?

Wenn es weiterhilft hier die beiden Clear Methoden:

Code:
// Deletes all links and the values they are pointing to

template< class T >
void LinkedList< T >::Clear( )
{
    Link< T >* link;
    
    link = last_;
        
    while( link != first_ )
    {
        link = link->GetPrevious();
        
        delete link->GetNext();
        
        count_--;
    }
    
    delete link;
    
    count_--;
}

// Deletes all links but does not the destroy the values pointed to

template< class T >
void LinkedList< T >::ClearLinks( )
{
    Link< T >* link;
    
    link = last_;
        
    while( link != first_ )
    {
        link->SetValue( 0 );
        link = link->GetPrevious();
        
        delete link->GetNext(); // link destructor enthält "delete value_;" und value_ sieht so aus T* value_;
        
        count_--;
    }
    
    link->SetValue( 0 );
    delete link;
    
    count_--;
}

Das delete nicht den Speicher zwingend überschreibt habe ich ja jetzt verstanden, nur wunderte es mich halt, dass die Werte nur nicht überschrieben werden, wenn ich die beiden Objekte mit Insert der Liste hinzugefügt habe...
Der Destructor wird ja jeweils aufgerufen, das habe ich mit cout sichergestellt, die jeweilige Adresse die ich durch this bekomme und ebenfalls dort ausgebe stimmt auch mit der überein die die Objekte vector1, vector2 usw. haben.

Vielleicht mache ich mir ja auch mehr Sorgen als ich müsste, aber bei C++ fliegt einem ja meistens gleich alles um die Ohren, wenn man Fehler macht und das ist zudem schwer demotivierend :p

Wer möchte kann auch den Source von mir erhalten um ihn sich durchzusehen, wäre natürlich nett, aber ich will das auch niemandem zwingend zumuten ;)
 
Zuletzt bearbeitet:
am sinnvollsten ist es die STL dafür zu nutzen. Die Template - Klasse list<T> ist ein guter Kandidat. Auch bzgl. der Einarbeitung in C++ ist das imo am sinnvollsten. Die STL gehört nämlich zu jedem standardkonformen C++ - System und steht damit auf jeden Fall zur Verfügung. Das implementieren von verpointeten Strukturen ist weniger relevant als viele denken. Auf der anderen Seite ist das gekonnte Anwenden der STL sehr hilfreich und keineswegs leicht. Ich empfehle in jedem Fall eine Kurskorrektur ;-)
 
und es ist bereits alles da. größtenteils zumindest. willst doch nicht bis lebenszeitende diverse listen implementieren. deine liste ist nicht zufällig ein teil einer 2D/3D visualisierung? frage wegen den gleichkomma-vektoren.
 
Ja, ich versuche mich hauptsächlich an 2D Spielen, daher der Vektor und die Liste.
Wollte gerne ein Projekt, dass ich in einer anderen Sprache angefangen hatte nach C++ bringen.

Nur um das klar zustellen, ich weiß, dass es Listen und vieles weiteres schon gibt, ich hatte halt bloß gedacht, dass ich dabei den Umgang mit Pointern z.B. besser lernen könnte.

Einerseits ist das wohl auch eingetreten, denn dieses Konstrukt ist das erste in dem Umfang, das mir nicht regelmäßig um die Ohren fliegt und wenn doch, dann weiß ich warum und kann es beheben.

Es ist halt schwierig bei C++ irgendwie anzufangen, weil ich meistens gar nicht weiß wo ich anfangen soll ;)
 
ah, verstehe.

zu dem anlass kann ich dir wärmstens OSG ans herz legen. dieses bekommst du von hier. nachdem du es installiert hast, setze die umgebungsvariablen wie folgt:

OSG_BINARY_DIR = xyz\OpenSceneGraph\bin;xyz\OpenSceneGraph\share\OpenSceneGraph\bin
OSG_FILE_PATH = xyz\OpenSceneGraph\data
OSG_INCLUDE_PATH = xyz\OpenSceneGraph\include
OSG_LIB_PATH = xyz\OpenSceneGraph\lib
OSG_NOTIFY_LEVEL = DEBUG
OSG_ROOT = xyz\OpenSceneGraph
PATH = abc;%OSG_BINARY_DIR%

beispiele findest du dann im verzeichnis \OpenSceneGraph\share\OpenSceneGraph\bin
die sourcen dazu gibts direkt auf deren seite im web.

OSG bringt dir alles mögliche mit, was du braucht. beliebige bildformate laden, vektor-arrays etc. sogar einen garbage-collector:
Code:
osg::ref_ptr<osg::Vec3dArray> Profile::getGeometryNormals(const osg::Vec3dArray &geometry_vertices)
{
	osg::ref_ptr<osg::Vec3dArray> geometry_normals = new osg::Vec3dArray;
blabla
	(*geometry_normals)[offset+i+k] += normal * weight;
blabla
	return geometry_normals.release();
}

jedenfalls wird geometry_normals automatisch ins datennirvana geschickt, wenn du es auf NULL setzt oder keine zeiger darauf zeigen. anderenfalls hast du jederzeit die möglichkeit zu erfahren, wieviele zeiger du momentan auf dieses datum hast und kannst selbst entscheiden ob du es killst.
 
Ein paar kurze Anmerkungen:

* Auf Elemente in einer verketteten Liste greift man nicht wahlfrei über Indizes zu, da die Zugriffszeit nicht konstant ist (man muss sich von Anfang oder Ende der Liste erst bis zum gewünschten Element durchhangeln). Wenn man durch die Liste laufen muss nimmt man Iteratoren. Wenn man konstanten wahlfreien Zugriff über einen Index will nimmt man ein Feld. Letzteres ist eventuell sogar günstiger, wenn nur Zeiger als Elemente in Frage kommen.

* Wenn man die Objekte, die in die Liste sollen, vorher mit new anlegt, sollten sie nicht hintenrum von der Liste mit delete freigeben werden, sondern nur die Zeiger aus der Liste entfernt werden und die Objekte im Speicher unberührt bleiben, da sie ja unabhängig von der Liste erzeugt wurden. Natürlich muss man sie nach Benutzung selbst mit delete freigeben.

* Wenn du doch mittels der von dir beschriebenen Clear-Funktion die Einträge inklusive Speicherfreigabe durch delete entfernst, solltest du in dem Moment, indem die Objekte in der Liste enthalten sind, keine weiteren Referenzen auf diese haben, da es sonst passieren kann, dass du über die expliziten Referenzen auf Objekte zugreifst, die evtl. schon durch die Liste freigegeben wurden und nicht mehr existieren.

* Wenn du delete für ein Objekt aufgerufen hast, ist nicht definiert, was passiert, wenn du weiterhin über den ursprünglichen Zeigerwert auf den jetzt freigegebenen Speicherbereich zugreifst. Da es auch prinzipiell ein Programmierfehler ist, dieses zu tun, ist es auch egal, was dort gelesen wird. Es läßt nur Hinweise auf Interna des Compilers oder der Speicherverwaltung zu, sollte aber nicht Teil eines normalen Programmablaufes sein. Es kann auch passieren, dass das Program Müll zurückliefert oder mit einer Schutzverletzung abstürzt.

P.S: Ich würde in der Klasse LinkedList<> bei der Implementierung die Elemente nicht intern über Zeiger verwalten, sondern den Benutzer der Klasse entscheiden lassen, ob er lieber Zeiger oder die Objekte selber in der Liste haben möchte. Z.B.: LinkedList<int> mit int-Elementen wäre effektiver als eine Implementierung mit int*-Elementen.
 
Zuletzt bearbeitet:
@ [GP] mino: Danke, das werde ich mir mal anschauen.

@ schlzber:

Zum 1.*: Tu ich das? Bei welchem Codeschnippsel mache greife ich denn per Index auf ein Objekt zu? Ich habe zwar die Methode ValueAtIndex, aber die rufe ich doch hier nirgends auf...?
Vielleicht verstehe ich es auch falsch, keine Ahnung.

Zum 2.*: Das ist mir bewusst, deshalb habe ich ja die Entfernung des Links ohne und mit Wertlöschung eingebaut, so kann ich ja das richtige wählen.
Habe ich z.B. ein Objekt aus der Liste ausgeben lassen und möchte es weiterhin außerhalb der Liste nutzen, entferne ich halt erst nur den Link mit diesem Objekt und lösche dann die Liste komplett wenn ich das will.

Z.B. so:
Code:
    LinkedList< ds::utl::Vector2f >* list = new LinkedList< ds::utl::Vector2f >( );   
    
    list->AddLast( new ds::utl::Vector2f( 50, 50 ) );
    list->AddLast( new ds::utl::Vector2f( 100, 100 ) );
    list->AddLast( new ds::utl::Vector2f( 150, 150 ) );
    list->AddLast( new ds::utl::Vector2f( 200, 200 ) );
    list->AddLast( new ds::utl::Vector2f( 250, 250 ) );
    
    // Vektor den ich behalten will
    ds::utl::Vector2f* vectorINeedLater;
    
    // Hol mir den Vektor den ich will
    for( ds::utl::Vector2f* vector; list->Next( ); )
    {
        vector = list->IteratorValue();
        
        if( vector->getX() == 150 && vector->getY() == 150 )
            vectorINeedLater = vector;
    }
    
    // Entferne nur den Link des Vektor den ich behalten will
    list->RemoveLink( vectorINeedLater );
    
    // Gibt 4 aus
    std::cout << list->GetCount() << std::endl;
    
    // Liste mit allen Werten löschen
    list->Clear();
    
    // Liste selbst löschen, könnte natürlich auch Clear( ) im Desktruktor 
    // aufrufen, allerdings kann man dann nicht entscheiden ob nur die Links 
    // oder auch die Werte gelöscht werden sollen
    delete list;
    
    // Gibt 150 | 150 aus
    std::cout << vectorINeedLater->getX() << " | " << vectorINeedLater->getY() << std::endl;
    
    // Vektor löschen
    delete vectorINeedLater;
    
    std::string input;
    std::cin >> input;

Zum 3.*: Das ist mir bewusst.

Zum 4.*: Das wurde mir ja mittlerweile mehrmals gesagt. Also kann ich, wenn die Adressen der Objekte übereinstimmen, sicher sein, dass sie gelöscht wurden, auch wenn die Werte bestehen bleiben?


Weiterhin soll das keine Liste sein die ich freigebe oder für andere zur Verfügung stelle, sie dient nur mir und das bisher wie erwähnt zum lernen.
Ich programmiere bisher auch nur als Hobby, werde aber dieses Jahr Informatik zu studieren beginnen und freue mich natürlich darauf, das ganze mehr zu verstehen.
 
DaysShadow schrieb:
@ [GP] mino: Danke, das werde ich mir mal anschauen.

@ schlzber:

Zum 1.*: Tu ich das? Bei welchem Codeschnippsel mache greife ich denn per Index auf ein Objekt zu? Ich habe zwar die Methode ValueAtIndex, aber die rufe ich doch hier nirgends auf...?
Vielleicht verstehe ich es auch falsch, keine Ahnung.
Bezog sich auf die im ersten Posting erwähnte Insert-Funktion, die an einem gewählten Index Elemente einfügt. Wenn man die beschriebenen Performance-Einschränkungen kennt und beachtet ist indexbasierter Zugriff auch nicht unbedingt falsch bei einer verketteten Liste. Halt nur nicht die naheliegenste Form des Zugriffs. ;).

DaysShadow schrieb:
Zum 2.*: Das ist mir bewusst, deshalb habe ich ja die Entfernung des Links ohne und mit Wertlöschung eingebaut, so kann ich ja das richtige wählen.
Habe ich z.B. ein Objekt aus der Liste ausgeben lassen und möchte es weiterhin außerhalb der Liste nutzen, entferne ich halt erst nur den Link mit diesem Objekt und lösche dann die Liste komplett wenn ich das will.

Z.B. so:
Code:
    LinkedList< ds::utl::Vector2f >* list = new LinkedList< ds::utl::Vector2f >( );   
    
    list->AddLast( new ds::utl::Vector2f( 50, 50 ) );
    list->AddLast( new ds::utl::Vector2f( 100, 100 ) );
    list->AddLast( new ds::utl::Vector2f( 150, 150 ) );
    list->AddLast( new ds::utl::Vector2f( 200, 200 ) );
    list->AddLast( new ds::utl::Vector2f( 250, 250 ) );
    
    // Vektor den ich behalten will
    ds::utl::Vector2f* vectorINeedLater;
    
    // Hol mir den Vektor den ich will
    for( ds::utl::Vector2f* vector; list->Next( ); )
    {
        vector = list->IteratorValue();
        
        if( vector->getX() == 150 && vector->getY() == 150 )
            vectorINeedLater = vector;
    }
    
    // Entferne nur den Link des Vektor den ich behalten will
    list->RemoveLink( vectorINeedLater );
    
    // Gibt 4 aus
    std::cout << list->GetCount() << std::endl;
    
    // Liste mit allen Werten löschen
    list->Clear();
    
    // Liste selbst löschen, könnte natürlich auch Clear( ) im Desktruktor 
    // aufrufen, allerdings kann man dann nicht entscheiden ob nur die Links 
    // oder auch die Werte gelöscht werden sollen
    delete list;
    
    // Gibt 150 | 150 aus
    std::cout << vectorINeedLater->getX() << " | " << vectorINeedLater->getY() << std::endl;
    
    // Vektor löschen
    delete vectorINeedLater;
    
    std::string input;
    std::cin >> input;
Ist vielleicht etwas penibel, aber ich würde die Clear-Funktion, die auch gleich die Elemente selbst löscht, als Hilfsfunktion außerhalb der Klasse definieren oder zumindest etwas anders als die Clear-Funktion bezeichnen, die nur die Einträge aus der Liste entfernt. Dann ist auch immer offensichtlich, welches Clear am Ende genommen wird. Und aus softwaretechnischer Sicht ist es "sauberer", die Liste im Destruktur nur die Dinge löschen zu lassen, die auch von ihr angelegt worden sind, d.h., in deinem Falle nur die Zeiger (oder Links).
Wenn es vom Konzept her vorgesehen ist, dass die Liste sozusagen auch Besitzer der referenzierten Objekte ab dem Moment wird, an dem sie hinzugefügt wurden, kann es auch sauber sein, dass sie deren Speicher verwaltet und sie auch freigibt. In dem Fall würde ich eine spezielle List-Klasse implementieren mit Elementfunktionen speziell für Vektoren:

Code:
class VectorList : public LinkedList<ds::utl::Vector2f>
{
public: 
void AddVector(float x, float y)
{
 LinkedList<ds::utl::Vector2f>::Add(new ds::utl::Vector2f(x, y);
}
};
zum Beispiel.

DaysShadow schrieb:
Zum 3.*: Das ist mir bewusst.
:)

DaysShadow schrieb:
Zum 4.*: Das wurde mir ja mittlerweile mehrmals gesagt. Also kann ich, wenn die Adressen der Objekte übereinstimmen, sicher sein, dass sie gelöscht wurden, auch wenn die Werte bestehen bleiben?

Also der Compiler oder die Laufzeit werden die mit new angelegten Vektor-Objekte nicht im Speicher verschieben. D.h., ja, wenn zwei Zeigervariablen die gleiche Adresse enthalten, und das Objekt über die erste Variable mit delete freigegeben wurde, auch die zweite Variable ab diesem Moment auf einen freigegeben Speicherbereich zeigt. Dass an dieser Adresse evtl. immer noch die Werte des Objekts stehen sagt nur aus, dass der Speicher freigegebener Objekte nicht automatisch mit Nullen überschrieben wird. Er wird nur als wieder verfügbar für neue Anforderungen markiert und jeder weitere Zugriff darauf ist ein Fehler.


DaysShadow schrieb:
Weiterhin soll das keine Liste sein die ich freigebe oder für andere zur Verfügung stelle, sie dient nur mir und das bisher wie erwähnt zum lernen.
Ich programmiere bisher auch nur als Hobby, werde aber dieses Jahr Informatik zu studieren beginnen und freue mich natürlich darauf, das ganze mehr zu verstehen.
Dann kannst du natürlich etwas unbekümmerter loslegen, aber versuch dir vorzustellen, du hast nach 6 Monaten Pause den Code wieder vor der Nase und versuchst nachzuvollziehen, was du da eigentlich getrieben hast! ;)
"Code is read much more often than it is written"
 
Zurück
Oben