Es gibt einen wichtigen Unterschied zwischen den beiden.
Alles, was nicht mit zugewiesen wurde, new
verhält sich ähnlich wie Werttypen in C # (und die Leute sagen oft, dass diese Objekte auf dem Stapel zugewiesen sind, was wahrscheinlich der häufigste / offensichtlichste Fall ist, aber nicht immer wahr ist. Genauer gesagt, Objekte, die ohne Verwendung zugewiesen wurden, new
haben automatischen Speicher Dauer
Alles, was mit zugeordnet new
ist, wird auf dem Heap zugewiesen, und ein Zeiger darauf wird zurückgegeben, genau wie bei Referenztypen in C #.
Alles, was auf dem Stapel zugewiesen ist, muss eine konstante Größe haben, die zur Kompilierungszeit festgelegt wird (der Compiler muss den Stapelzeiger korrekt setzen, oder wenn das Objekt Mitglied einer anderen Klasse ist, muss er die Größe dieser anderen Klasse anpassen). . Aus diesem Grund sind Arrays in C # Referenztypen. Sie müssen es sein, denn mit Referenztypen können wir zur Laufzeit entscheiden, wie viel Speicher benötigt werden soll. Gleiches gilt hier. Nur Arrays mit konstanter Größe (eine Größe, die zur Kompilierungszeit bestimmt werden kann) können mit automatischer Speicherdauer (auf dem Stapel) zugewiesen werden. Arrays mit dynamischer Größe müssen auf dem Heap durch Aufrufen zugewiesen werden new
.
(Und hier hört jede Ähnlichkeit mit C # auf)
Jetzt hat alles, was auf dem Stapel zugewiesen ist, eine "automatische" Speicherdauer (Sie können eine Variable tatsächlich als deklarieren auto
, dies ist jedoch die Standardeinstellung, wenn kein anderer Speichertyp angegeben ist, sodass das Schlüsselwort in der Praxis nicht wirklich verwendet wird, aber hier ist es kommt von)
Automatische Speicherdauer bedeutet genau, wie es sich anhört. Die Dauer der Variablen wird automatisch behandelt. Im Gegensatz dazu muss alles, was auf dem Heap zugewiesen ist, von Ihnen manuell gelöscht werden. Hier ist ein Beispiel:
void foo() {
bar b;
bar* b2 = new bar();
}
Diese Funktion erzeugt drei erwägenswerte Werte:
In Zeile 1 wird eine Variable b
vom Typ bar
auf dem Stapel deklariert (automatische Dauer).
In Zeile 2 deklariert es einen bar
Zeiger b2
auf dem Stapel (automatische Dauer) und ruft new auf, wobei ein bar
Objekt auf dem Heap zugewiesen wird. (dynamische Dauer)
Wenn die Funktion zurückkehrt, geschieht Folgendes: Erstens b2
wird der Gültigkeitsbereich verlassen (die Reihenfolge der Zerstörung ist immer entgegengesetzt zur Reihenfolge der Konstruktion). Ist b2
aber nur ein Zeiger, so passiert nichts, der Speicher, den es belegt, wird einfach freigegeben. Und wichtig ist, dass der Speicher, auf den er verweist (die bar
Instanz auf dem Heap), NICHT berührt wird. Nur der Zeiger wird freigegeben, da nur der Zeiger eine automatische Dauer hatte. Zweitens b
wird der Gültigkeitsbereich verlassen. Da es eine automatische Dauer hat, wird sein Destruktor aufgerufen und der Speicher wird freigegeben.
Und die bar
Instanz auf dem Haufen? Es ist wahrscheinlich immer noch da. Niemand hat sich die Mühe gemacht, es zu löschen, also haben wir Speicher verloren.
Aus diesem Beispiel können wir ersehen, dass bei allen Objekten mit automatischer Dauer der Destruktor garantiert aufgerufen wird, wenn der Gültigkeitsbereich überschritten wird. Das ist nützlich. Aber alles, was auf dem Heap zugewiesen ist, hält so lange an, wie wir es brauchen, und kann dynamisch dimensioniert werden, wie im Fall von Arrays. Das ist auch nützlich. Damit können wir unsere Speicherzuordnungen verwalten. Was ist, wenn die Foo-Klasse in ihrem Konstruktor Speicher auf dem Heap zugewiesen und diesen Speicher in ihrem Destruktor gelöscht hat? Dann könnten wir das Beste aus beiden Welten bekommen, sichere Speicherzuordnungen, die garantiert wieder freigegeben werden, aber ohne die Einschränkungen, alles auf den Stapel zu zwingen.
Und genau so funktioniert der meiste C ++ - Code. Schauen Sie sich std::vector
zum Beispiel die Standardbibliothek an . Dies wird normalerweise auf dem Stapel zugewiesen, kann jedoch dynamisch angepasst und in der Größe geändert werden. Dazu wird nach Bedarf intern Speicher auf dem Heap zugewiesen. Der Benutzer der Klasse sieht dies nie, daher besteht keine Möglichkeit, dass Speicher verloren geht oder Sie vergessen, die von Ihnen zugewiesenen Daten zu bereinigen.
Dieses Prinzip wird als RAII (Resource Acquisition is Initialization) bezeichnet und kann auf jede Ressource erweitert werden, die erworben und freigegeben werden muss. (Netzwerk-Sockets, Dateien, Datenbankverbindungen, Synchronisationssperren). Alle können im Konstruktor erworben und im Destruktor freigegeben werden, sodass Sie sicher sind, dass alle Ressourcen, die Sie erwerben, wieder freigegeben werden.
Verwenden Sie in der Regel niemals new / delete direkt aus Ihrem High-Level-Code. Wickeln Sie es immer in eine Klasse ein, die den Speicher für Sie verwalten kann und die sicherstellt, dass er wieder freigegeben wird. (Ja, es kann Ausnahmen von dieser Regel geben. Insbesondere bei intelligenten Zeigern müssen Sie new
direkt aufrufen und den Zeiger an seinen Konstruktor übergeben, der dann übernimmt und sicherstellt delete
, dass er korrekt aufgerufen wird. Dies ist jedoch immer noch eine sehr wichtige Faustregel )