Es gibt einen wichtigen Unterschied zwischen den beiden.
Alles, was nicht mit zugewiesen wurde, newverhä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, newhaben automatischen Speicher Dauer
Alles, was mit zugeordnet newist, 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 bvom Typ barauf dem Stapel deklariert (automatische Dauer).
In Zeile 2 deklariert es einen barZeiger b2auf dem Stapel (automatische Dauer) und ruft new auf, wobei ein barObjekt auf dem Heap zugewiesen wird. (dynamische Dauer)
Wenn die Funktion zurückkehrt, geschieht Folgendes: Erstens b2wird der Gültigkeitsbereich verlassen (die Reihenfolge der Zerstörung ist immer entgegengesetzt zur Reihenfolge der Konstruktion). Ist b2aber 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 barInstanz auf dem Heap), NICHT berührt wird. Nur der Zeiger wird freigegeben, da nur der Zeiger eine automatische Dauer hatte. Zweitens bwird der Gültigkeitsbereich verlassen. Da es eine automatische Dauer hat, wird sein Destruktor aufgerufen und der Speicher wird freigegeben.
Und die barInstanz 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::vectorzum 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 newdirekt 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 )