Ich benutze C ++ seit kurzer Zeit und habe mich über das neue Schlüsselwort gewundert. Soll ich es einfach benutzen oder nicht?
1) Mit dem neuen Schlüsselwort ...
MyClass* myClass = new MyClass();
myClass->MyField = "Hello world!";
2) Ohne das neue Schlüsselwort ...
MyClass myClass;
myClass.MyField = "Hello world!";
Aus Sicht der Implementierung scheinen sie nicht so unterschiedlich zu sein (aber ich bin mir sicher, dass sie es sind) ... Meine Hauptsprache ist jedoch C #, und natürlich ist die erste Methode das, was ich gewohnt bin.
Die Schwierigkeit scheint zu sein, dass Methode 1 mit den Standard-C ++ - Klassen schwieriger zu verwenden ist.
Welche Methode soll ich verwenden?
Update 1:
Ich habe kürzlich das neue Schlüsselwort für Heapspeicher (oder freien Speicher ) für ein großes Array verwendet, das außerhalb des Gültigkeitsbereichs lag (dh von einer Funktion zurückgegeben wurde). Wo ich zuvor den Stack verwendet habe, der dazu führte, dass die Hälfte der Elemente außerhalb des Gültigkeitsbereichs beschädigt wurde, stellte der Wechsel zur Heap-Nutzung sicher, dass die Elemente in Kontakt waren. Yay!
Update 2:
Ein Freund von mir hat mir kürzlich gesagt, dass es eine einfache Regel für die Verwendung des new
Schlüsselworts gibt. Geben Sie jedes Mal ein new
, wenn Sie tippen delete
.
Foobar *foobar = new Foobar();
delete foobar; // TODO: Move this to the right place.
Dies hilft, Speicherlecks zu vermeiden, da Sie den Löschvorgang immer irgendwo ablegen müssen (dh wenn Sie ihn ausschneiden und entweder in einen Destruktor oder auf andere Weise einfügen).
std::vector
undstd::shared_ptr
. Diese schließen die Anrufe annew
unddelete
für Sie ab, sodass die Wahrscheinlichkeit, dass Speicher verloren geht, noch geringer ist. Fragen Sie sich zum Beispiel: Denken Sie immer daran,delete
überall dort, wo eine Ausnahme ausgelöst werden könnte , ein entsprechendes zu setzen ? Esdelete
ist schwieriger, s von Hand einzugeben, als Sie vielleicht denken.Antworten:
Methode 1 (unter Verwendung
new
)delete
Ihr Objekt später explizit angeben. (Wenn Sie es nicht löschen, können Sie einen Speicherverlust verursachen.)delete
es. (dh Sie könntenreturn
ein Objekt, das Sie mit erstellt habennew
)delete
d; und es sollte immer gelöscht werden , unabhängig davon, welcher Steuerpfad verwendet wird oder ob Ausnahmen ausgelöst werden.Methode 2 (nicht verwendet
new
)delete
es später nicht brauchen .return
Zeiger auf ein Objekt auf dem Stapel haben)Soweit man verwendet; Sie wählen die Methode aus, die für Sie unter Berücksichtigung der oben genannten Einschränkungen am besten geeignet ist.
Einige einfache Fälle:
delete
(und möglicherweise Speicherlecks verursachen ), sollten Sie diese nicht verwendennew
.new
quelle
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 zugeordnetnew
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:
Diese Funktion erzeugt drei erwägenswerte Werte:
In Zeile 1 wird eine Variable
b
vom Typbar
auf dem Stapel deklariert (automatische Dauer).In Zeile 2 deklariert es einen
bar
Zeigerb2
auf dem Stapel (automatische Dauer) und ruft new auf, wobei einbar
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). Istb2
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 (diebar
Instanz auf dem Heap), NICHT berührt wird. Nur der Zeiger wird freigegeben, da nur der Zeiger eine automatische Dauer hatte. Zweitensb
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 sicherstelltdelete
, dass er korrekt aufgerufen wird. Dies ist jedoch immer noch eine sehr wichtige Faustregel )quelle
Dies wird fast nie durch Ihre Eingabeeinstellungen bestimmt, sondern durch den Kontext. Wenn Sie das Objekt über einige Stapel hinweg aufbewahren müssen oder wenn es für den Stapel zu schwer ist, weisen Sie es dem freien Speicher zu. Da Sie ein Objekt zuweisen, sind Sie auch für die Freigabe des Speichers verantwortlich. Suchen Sie den
delete
Operator.Um die Verwendung von Free-Store-Management zu vereinfachen, haben die Leute Dinge wie
auto_ptr
und erfundenunique_ptr
. Ich empfehle Ihnen dringend, sich diese anzuschauen. Sie könnten sogar bei Ihren Tippproblemen hilfreich sein ;-)quelle
Wenn Sie in C ++ schreiben, schreiben Sie wahrscheinlich für die Leistung. Die Verwendung von new und free store ist viel langsamer als die Verwendung des Stacks (insbesondere bei Verwendung von Threads). Verwenden Sie ihn daher nur, wenn Sie ihn benötigen.
Wie andere bereits gesagt haben, benötigen Sie new, wenn Ihr Objekt außerhalb der Funktion oder des Objektbereichs leben muss, das Objekt sehr groß ist oder wenn Sie die Größe eines Arrays zur Kompilierungszeit nicht kennen.
Vermeiden Sie außerdem, jemals Löschen zu verwenden. Wickeln Sie Ihr neues stattdessen in einen intelligenten Zeiger. Lassen Sie den Smart-Pointer-Aufruf für Sie löschen.
In einigen Fällen ist ein intelligenter Zeiger nicht intelligent. Speichern Sie std :: auto_ptr <> niemals in einem STL-Container. Der Zeiger wird aufgrund von Kopiervorgängen im Container zu früh gelöscht. Ein anderer Fall ist, wenn Sie einen wirklich großen STL-Container mit Zeigern auf Objekte haben. boost :: shared_ptr <> hat eine Menge Geschwindigkeitsaufwand, da die Referenzzählungen nach oben und unten erhöht werden. In diesem Fall ist es besser, den STL-Container in ein anderes Objekt einzufügen und diesem Objekt einen Destruktor zu geben, der bei jedem Zeiger im Container delete aufruft.
quelle
Die kurze Antwort ist: wenn Sie ein Anfänger in C ++ sind, sollten Sie nie verwenden werden
new
oderdelete
sich selbst.Verwenden Sie stattdessen intelligente Zeiger wie z
std::unique_ptr
undstd::make_unique
(oder seltenerstd::shared_ptr
undstd::make_shared
) verwenden. Auf diese Weise müssen Sie sich nicht annähernd so viele Gedanken über Speicherlecks machen. Und selbst wenn Sie fortgeschrittener sind, besteht die beste Vorgehensweise normalerweise darin, die benutzerdefinierte Artnew
und Weise, die Sie verwenden,delete
in eine kleine Klasse (z. B. einen benutzerdefinierten intelligenten Zeiger) zu kapseln, die sich ausschließlich mit Problemen des Objektlebenszyklus befasst.Natürlich führen diese intelligenten Zeiger hinter den Kulissen immer noch eine dynamische Zuweisung und Freigabe durch, sodass der Code, der sie verwendet, immer noch den damit verbundenen Laufzeitaufwand hat. Andere Antworten hier haben diese Probleme behandelt und wie man Entwurfsentscheidungen darüber trifft, wann intelligente Zeiger verwendet werden sollen, anstatt nur Objekte auf dem Stapel zu erstellen oder sie als direkte Mitglieder eines Objekts einzubeziehen, gut genug, dass ich sie nicht wiederholen werde. Aber meine Zusammenfassung wäre: Verwenden Sie keine intelligenten Zeiger oder dynamische Zuordnung, bis Sie etwas dazu zwingt.
quelle
Ohne das
new
Schlüsselwort speichern Sie das auf dem Aufrufstapel . Das Speichern zu großer Variablen auf dem Stapel führt zu einem Stapelüberlauf .quelle
Die einfache Antwort lautet Ja - new () erstellt ein Objekt auf dem Heap (mit dem unglücklichen Nebeneffekt, dass Sie seine Lebensdauer verwalten müssen (indem Sie explizit delete darauf aufrufen), während das zweite Formular ein Objekt im Stapel im aktuellen erstellt scope und dieses Objekt werden zerstört, wenn es den Gültigkeitsbereich verlässt.
quelle
Wenn Ihre Variable nur im Kontext einer einzelnen Funktion verwendet wird, ist es besser, eine Stapelvariable zu verwenden, dh Option 2. Wie andere bereits gesagt haben, müssen Sie die Lebensdauer von Stapelvariablen nicht verwalten - sie sind konstruiert und automatisch zerstört. Das Zuweisen / Freigeben einer Variablen auf dem Heap ist im Vergleich dazu langsam. Wenn Ihre Funktion häufig genug aufgerufen wird, werden Sie eine enorme Leistungsverbesserung feststellen, wenn Sie Stapelvariablen im Vergleich zu Heap-Variablen verwenden.
Es gibt jedoch einige offensichtliche Fälle, in denen Stapelvariablen nicht ausreichen.
Wenn die Stapelvariable einen großen Speicherbedarf hat, besteht die Gefahr, dass der Stapel überläuft. Standardmäßig beträgt die Stapelgröße jedes Threads 1 MB unter Windows . Es ist unwahrscheinlich, dass Sie eine Stapelvariable mit einer Größe von 1 MB erstellen. Sie müssen jedoch berücksichtigen, dass die Stapelauslastung kumulativ ist. Wenn Ihre Funktion eine Funktion aufruft, die eine andere Funktion aufruft, die eine andere Funktion aufruft, die ..., belegen die Stapelvariablen in all diesen Funktionen Speicherplatz auf demselben Stapel. Rekursive Funktionen können schnell auf dieses Problem stoßen, je nachdem, wie tief die Rekursion ist. Wenn dies ein Problem ist, können Sie den Stapel vergrößern (nicht empfohlen) oder die Variable auf dem Heap mit dem neuen Operator zuweisen (empfohlen).
Die andere, wahrscheinlichere Bedingung ist, dass Ihre Variable über den Umfang Ihrer Funktion hinaus "leben" muss. In diesem Fall würden Sie die Variable auf dem Heap so zuweisen, dass sie außerhalb des Bereichs einer bestimmten Funktion erreicht werden kann.
quelle
Übergeben Sie myClass aus einer Funktion oder erwarten Sie, dass es außerhalb dieser Funktion existiert? Wie einige andere sagten, dreht sich alles um den Umfang, wenn Sie nicht auf dem Heap zuordnen. Wenn Sie die Funktion verlassen, verschwindet sie (eventuell). Einer der klassischen Fehler von Anfängern ist der Versuch, ein lokales Objekt einer Klasse in einer Funktion zu erstellen und zurückzugeben, ohne es dem Heap zuzuweisen. Ich kann mich daran erinnern, wie ich solche Dinge in meinen früheren Tagen mit C ++ debuggt habe.
quelle
Die zweite Methode erstellt die Instanz auf dem Stapel zusammen mit deklarierten Elementen
int
und der Liste der Parameter, die an die Funktion übergeben werden.Die erste Methode bietet Platz für einen Zeiger auf dem Stapel, den Sie auf den Speicherort im Speicher gesetzt haben, an dem ein neuer
MyClass
auf dem Heap zugewiesen wurde - oder auf dem freien Speicher.Die erste Methode erfordert auch, dass Sie
delete
das erstellennew
, womit Sie erstellen , während bei der zweiten Methode die Klasse automatisch zerstört und freigegeben wird, wenn sie außerhalb des Gültigkeitsbereichs liegt (normalerweise die nächste schließende Klammer).quelle
Die kurze Antwort lautet: Ja, das Schlüsselwort "new" ist unglaublich wichtig, da bei der Verwendung die Objektdaten auf dem Heap gespeichert werden und nicht auf dem Stapel, was am wichtigsten ist!
quelle