9 Stimmen

Deallokation von Objekten, die in einem Vektor gespeichert sind?

Ich habe eine Klasse, die einen Vektor von Objekten erstellt. Im Deconstructor für diese Klasse versuche ich, den den Objekten zugewiesenen Speicher freizugeben. Ich versuche, dies zu tun, indem ich einfach eine Schleife durch den Vektor mache. Wenn also der Vektor maps aufgerufen wird, tue ich das:

Building::~Building() {
    int i;
    for (i=0; i<maps.size(); i++) {
        delete[] &maps[i];
    }
}

Wenn ich das Programm ausführe, kommt es zu Fehlern beim Freigeben von Speicher. Ich glaube, ich lösche das Array, in dem die Objekte gespeichert sind, und nicht die Objekte selbst. Ist das korrekt? Wenn nicht, haben Sie eine Idee, was ich falsch mache?

21voto

Alex Reece Punkte 1826

Das hängt davon ab, wie der Vektor definiert ist.

Wenn Karten ein vector<myClass*> löschen Sie jedes Element mit etwas Ähnlichem wie:

for ( i = 0; i < maps.size(); i++)
{
    delete maps[i];
}

Wenn Karten ein vector<myClass> Ich glaube nicht, dass Sie die einzelnen Elemente löschen müssen.

18voto

Eric Rahm Punkte 690

Anhand der von Ihnen verwendeten Terminologie und des dargestellten Codes ist schwer zu erkennen, was genau vor sich geht. Vielleicht würden Ihnen ein paar Beispiele weiterhelfen.

Array neu und Array löschen

Was ist los mit new [] y delete [] fragen Sie? Diese Typen werden für die Zuweisung/Deallokation von Arrays von Dingen verwendet. Diese Dinge können PODs oder vollwertige Objekte sein. Bei Objekten rufen sie den Konstruktor nach der Zuweisung und den Destruktor beim Deallokieren auf.

Nehmen wir ein erfundenes Beispiel:

class MrObject
{
public:
   MrObject() : myName(new char[9]) { memcpy(myName, "MrObject", 9); }
   virtual ~MrObject() { std::cout << "Goodbye cruel world!\n"; delete [] myName; }
private:
   char* myName;
};

Jetzt können wir einige lustige Dinge mit MrObject machen.

Arrays von Objekten

Lassen Sie uns zunächst ein einfaches Array erstellen:

MrObject* an_array = new MrObject[5];

Damit haben wir ein Array von 5 MrObjects, die alle schön initialisiert sind. Wenn wir dieses Array löschen wollen, sollten wir ein Array delete ausführen, was wiederum den Destruktor für jedes MrObject aufruft. Versuchen wir das mal:

delete [] an_array;

Was aber, wenn wir es vermasseln und einfach eine normale Löschung vornehmen? Jetzt ist ein guter Zeitpunkt, es selbst zu versuchen

delete an_array;

Sie werden sehen, dass nur der erste Destruktor aufgerufen wird. Das liegt daran, dass wir nicht das gesamte Array gelöscht haben, sondern nur den ersten Eintrag.

Nun ja, manchmal. Es ist wirklich undefiniert, was hier passiert. Die Schlussfolgerung ist, die Array-Form von delete zu verwenden, wenn Sie ein Array new verwenden, dito für einfaches altes new und delete.

Vektoren von Objekten

OK, das hat Spaß gemacht. Aber lassen Sie uns jetzt einen Blick auf den std::vector werfen. Sie werden feststellen, dass dieser Kerl den Speicher für Sie verwaltet, und wenn er den Gültigkeitsbereich verlässt, wird alles, was er festhält, ebenfalls gelöscht. Lassen Sie uns eine Testfahrt mit ihm machen:

std::vector<MrObject> a_vector(5);

Jetzt haben Sie einen Vektor mit 5 initialisierten MrObjects. Schauen wir mal, was passiert, wenn wir das Ding löschen:

a_vector.clear();

Sie werden feststellen, dass alle 5 Zerstörer getroffen wurden.

Vektoren von Zeigern auf Objekte

Oooooh, sagst du, jetzt werden wir aber schick. Ich möchte alle Vorzüge von std::vector nutzen, aber ich möchte auch den gesamten Speicher selbst verwalten! Nun, auch dafür gibt es eine Zeile:

std::vector<MrObject*> a_vector_of_pointers(5);
for (size_t idx = 0; idx < 5; idx++) {
   // note: it's just a regular new here, not an arra
   a_vector_of_pointers[idx] = new MrObject;
}

Das war schon etwas mühsamer. Aber es kann nützlich sein, Sie könnten einen Nicht-Standard-Konstruktor beim Erstellen von MrObject verwenden. Du könntest stattdessen abgeleitete MrObjects dort hineinstellen. Nun, wie Sie sehen können, sind dem Himmel keine Grenzen gesetzt. Aber halt! Sie haben diesen Speicher angelegt, also sollten Sie ihn auch verwalten. Sie sollten eine Schleife über jeden Eintrag im Vektor laufen lassen und danach selbst aufräumen:

for (size_t idx = 0; idx < a_vector_of_pointers.size(); idx++) {
   delete a_vector_of_pointers[idx];
}

6voto

San Jacinto Punkte 8628

In C++ können Sie Daten nur per Zeiger löschen. Sie haben dies mit dem &-Operator erreicht, aber wenn Ihr Vektor keine Zeiger enthält, die auf Speicher verweisen, der auf dem Heap des Rechners zugewiesen ist (nicht auf dem Stack, wie es bei einer normalen Variablendeklaration der Fall ist), dann können Sie versuchen, ihn zu löschen, aber Sie werden auf ein undefiniertes Verhalten stoßen (was hoffentlich einen Programmabsturz verursacht).

Wenn Sie in einen Vektor einfügen, ruft der Vektor den Kopierkonstruktor der Klasse auf und Sie fügen tatsächlich eine Kopie des Objekts ein. Wenn Sie eine Funktion haben, deren einziger Zweck wie der folgende ist:

void insertObj(obj & myObject)
{
  myVector.insert(myObject);
}

Dann erkennen Sie, dass es zwei obj's in diesem Bereich: die eine, die Sie in durch Referenz übergeben, und die Kopie in den Vektor. Wenn wir stattdessen myObject als Wert und nicht als Referenz übergeben hätten, dann könnten wir sagen, dass zwei Kopien des Objekts in diesem Bereich existieren und eine im Aufrufer. In jeder dieser 3 Instanzen sind sie no das gleiche Objekt.

Wenn Sie stattdessen Zeiger im Container speichern, wird der Vektor eine Kopie des Zeigers erstellen ( NICHT eine Kopie des Objekts) und fügt den kopierten Zeiger in den Vektor ein. Es ist normalerweise keine gute Praxis, Elemente per Zeiger in einen Container einzufügen, es sei denn, Sie connaître dass das Objekt mindestens so lange leben wird, bis der Container damit fertig ist. Zum Beispiel,

void insert()
{
  Obj myObj;
  myVector.insert(&myObj);
}

Ist wahrscheinlich eine sehr schlechte Idee, da Sie einen Zeiger im Vektor haben, der auf ein Objekt zeigt, das automatisch zerstört wird, wenn es den Anwendungsbereich verlässt!

Der Punkt ist, wenn Sie malloc'd oder new'd Ihr Objekt, dann müssen Sie frei oder löschen Sie es. Wenn Sie es auf dem Stack erstellt haben, dann tun Sie nichts. Der Vektor wird sich darum kümmern, wenn er zerstört wird.

Für ein tieferes Verständnis der Stack-basierten Zuweisung gegenüber der Heap-basierten Zuweisung siehe meine Antwort hier: Wie funktioniert eigentlich die automatische Speicherzuweisung in C++?

1voto

for(std::vector<MyObjectClass>::iterator beg = myVector->begin(), end = myVector->end(); beg != end; beg++)
{
    delete *beg;
}
myVector->clear();

1voto

Guy Avraham Punkte 3086

Ich habe mich entschlossen, meinen Kommentar in eine Antwort zu verwandeln (zusammen mit den anderen großartigen Antworten hier), also hier ist sie.

Ich möchte nochmals darauf hinweisen, dass es in diesem Fall um die Vererbung des Objekts geht.

Wenn Sie ein Array eines abgeleiteten Objekts löschen, auf das ein Base-Zeiger zeigt, gehen Sie wie folgt vor:

Base* pArr = new Derived[3];

delete [] pArr;

Was der Compiler "unter der Haube" tut, ist, den folgenden Code zu erzeugen:

//destruct the objects in *pArr in the inverse order
//in which they were constructed
for (int i = the number of elements in the array - 1; i >= 0; --i)
{
     pArr[i].Base::~Base(); 
}

Wenn wir dies tun, kommt es zu einem undefinierten Verhalten. Der Umgang mit Arrays ist einfach der Umgang mit Offsets. Wenn diese Schleife auftritt, wird der Zeiger des Arrays in jeder Iteration der Schleife entsprechend der Größe von Base inkrementiert --> und hier wird es "undefiniert". In dem "einfachen" (aber weniger häufigen) Fall, in dem die abgeleitete Klasse keine eigenen Mitglieder hinzufügt, entspricht ihre Größe der Größe von Base --> so dass die Dinge gut funktionieren könnten (ich vermute, dass das nicht immer der Fall ist). Aber (!!) wenn man der abgeleiteten Klasse mindestens ein Mitglied hinzufügt, wächst ihre Größe, so dass das Offset-Inkrement in jeder Iteration falsch ist.

Zur Veranschaulichung dieses Falles habe ich die folgenden Base- und Derived-Objekte erstellt. Beachten Sie, dass in dem Fall, dass Derived enthält nicht le site m_c Mitglied, der Löschvorgang geht gut (kommentieren Sie es aus und sehen Sie selbst), JEDOCH sobald Sie es hinzufügen, bekam ich einen Segmentierungsfehler (das ist das undefinierte Verhalten).

#include <iostream>
using namespace std;

class Base 
{

    public:
        Base(int a, int b)
        : m_a(a)
        , m_b(b)    
        {
           cout << "Base::Base - setting m_a:" << m_a << " m_b:" << m_b << endl;
        }

        virtual ~Base()
        {
            cout << "Base::~Base" << endl;
        }

        protected:
            int m_a;
            int m_b;
};

class Derived : public Base
{
    public:
    Derived() 
    : Base(1, 2) , m_c(3)   
    {

    }

    virtual ~Derived()
    {
        cout << "Derived::Derived" << endl;
    }

    private:    
    int m_c;
};

int main(int argc, char** argv)
{
    // create an array of Derived object and point them with a Base pointer
    Base* pArr = new Derived [3];

    // now go ahead and delete the array using the "usual" delete notation for an array
    delete [] pArr;

    return 0;
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X