Wann sollte ich das neue Schlüsselwort in C ++ verwenden?

Ich benutze C ++ für eine kurze Zeit und ich habe mich über das neue Keyword gewundert. Einfach, sollte ich es benutzen oder nicht?

1) Mit dem neuen Keyword …

MyClass* myClass = new MyClass(); myClass->MyField = "Hello world!"; 

2) Ohne das neue Keyword …

 MyClass myClass; myClass.MyField = "Hello world!"; 

Aus der Perspektive der Implementierung scheinen sie nicht so unterschiedlich zu sein (aber ich bin mir sicher, dass sie es sind) … Meine primäre Sprache ist jedoch C #, und natürlich ist die erste Methode das, was ich gewohnt bin.

Die Schwierigkeit scheint zu sein, dass Methode 1 schwerer mit den std C ++ – classn zu verwenden ist.

Welche Methode sollte ich verwenden?

Update 1:

Ich habe kürzlich das neue Schlüsselwort für den Heapspeicher (oder den freien Speicher ) für ein großes Array verwendet, das den Gültigkeitsbereich verlassen hat (dh von einer function zurückgegeben wurde). Wo ich zuvor den Stack verwendet habe, was dazu führte, dass die Hälfte der Elemente außerhalb des Bereichs korrupt war, stellte das Umschalten auf die Heap-Nutzung sicher, dass die Elemente intakt waren. Yay!

Update 2:

Ein Freund von mir hat mir kürzlich gesagt, dass es eine einfache Regel für die Verwendung des new Keywords gibt. Jedes Mal, wenn Sie new delete , geben Sie delete .

 Foobar *foobar = new Foobar(); delete foobar; // TODO: Move this to the right place. 

Dies hilft, Speicherlecks zu vermeiden, da Sie das Löschen immer irgendwo platzieren müssen (dh wenn Sie es ausschneiden und in einen Destruktor oder auf andere Weise einfügen).

   

Methode 1 (mit new )

  • Ordnet Speicher für das Objekt im freien Speicher zu (Dies ist häufig dasselbe wie der Heap )
  • Erfordert, dass Sie Ihr Objekt später explizit delete . (Wenn Sie es nicht löschen, könnten Sie ein Speicherleck erstellen)
  • Speicher bleibt reserviert, bis Sie ihn delete . (dh Sie könnten ein Objekt, das Sie erstellt haben, mit einem new Objekt zurückgeben)
  • Das Beispiel in der Frage wird Speicher verlieren, wenn der pointers delete d ist; und es sollte immer gelöscht werden , unabhängig davon, welcher Kontrollpfad verwendet wird oder ob Ausnahmen ausgetriggers werden.

Methode 2 (nicht new )

  • Ordnet Speicher für das Objekt auf dem Stapel zu (wobei alle lokalen Variablen gehen). Für den Stapel ist im Allgemeinen weniger Speicher verfügbar. Wenn Sie zu viele Objekte zuweisen, riskieren Sie einen Stapelüberlauf.
  • Sie müssen es später nicht mehr delete .
  • Der Speicher wird nicht mehr zugewiesen, wenn er den Gültigkeitsbereich verlässt. (dh Sie sollten keinen pointers auf ein Objekt auf dem Stapel zurückgeben)

Soweit was zu verwenden; Sie wählen die Methode, die am besten für Sie funktioniert, angesichts der oben genannten Einschränkungen.

Einige einfache Fälle:

  • Wenn Sie sich nicht über das Aufrufen von delete Sorgen machen möchten (und die Möglichkeit, Speicherlecks zu verursachen), sollten Sie nicht new .
  • Wenn Sie von einer function einen pointers auf Ihr Objekt zurückgeben möchten, müssen Sie new

Es gibt einen wichtigen Unterschied zwischen den beiden.

Alles, was nicht mit new zugewiesen wird, verhält sich ähnlich wie Werttypen in C # (und Leute sagen oft, dass diese Objekte auf dem Stack zugewiesen sind, was wahrscheinlich der häufigste / offensichtlichste Fall ist, aber nicht immer wahr. Genauer gesagt, Objekte, die ohne new zugewiesen wurden Automatische Speicherdauer Alles, was mit new zugewiesen wurde, wird auf dem Heap zugewiesen, und ein pointers auf es wird zurückgegeben, genau wie Referenztypen in C #.

Alles, was auf dem Stapel zugewiesen wird, muss eine konstante Größe haben, die zur Kompilierungszeit bestimmt wird (der Compiler muss den Stapelzeiger richtig setzen, oder wenn das Objekt ein Mitglied einer anderen class ist, muss er die Größe dieser anderen class anpassen) . Aus diesem Grund sind Arrays in C # Referenztypen. Sie müssen es sein, denn bei Referenztypen können wir zur Laufzeit entscheiden, wie viel Speicher Sie benötigen. Und das gilt auch hier. Nur Arrays mit konstanter Größe (eine Größe, die zur Kompilierzeit 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 von new zugewiesen werden.

(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 auto deklarieren, aber dies ist der Standard, wenn kein anderer Speichertyp angegeben ist, so dass das Schlüsselwort in der Praxis nicht wirklich verwendet wird, aber hier es kommt von)

Automatische Speicherdauer bedeutet genau, wie es klingt, die Dauer der Variablen wird automatisch behandelt. Im Gegensatz dazu muss alles, was auf dem Heap zugewiesen wurde, manuell von Ihnen gelöscht werden. Hier ist ein Beispiel:

 void foo() { bar b; bar* b2 = new bar(); } 

Diese function erstellt drei denkwürdige Werte:

In Zeile 1 deklariert er eine Variable b vom Typ bar auf dem Stapel (automatische Dauer).

In Zeile 2 deklariert es einen Balkenzeiger b2 auf dem Stapel (automatische Dauer) und ruft neu auf, wobei ein b2 auf dem Stapel zugewiesen wird. (dynamische Dauer)

Wenn die function zurückkehrt, passiert Folgendes: Zuerst geht b2 aus dem Geltungsbereich (die Reihenfolge der Zerstörung ist immer entgegengesetzt der Reihenfolge der Konstruktion). Aber b2 ist nur ein pointers, also passiert nichts, der Speicher, den er belegt, wird einfach freigegeben. Und wichtig ist, dass der Speicher, auf den er zeigt (die bar Instanz auf dem Heap), NICHT berührt wird. Nur der pointers wird freigegeben, da nur der pointers eine automatische Dauer hatte. Zweitens geht b außerhalb des Gültigkeitsbereichs, und 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. Keiner hat sich die Mühe gemacht, es zu löschen, deshalb haben wir Speicher verloren.

Aus diesem Beispiel können wir sehen, dass bei allen Objekten mit automatischer Dauer der Destruktor automatisch aufgerufen wird, wenn der Gültigkeitsbereich überschritten wird. Das ist nützlich. Aber alles, was auf dem Heap zugewiesen ist, dauert so lange, wie wir es benötigen, und kann wie bei Arrays dynamisch skaliert werden. Das ist auch nützlich. Wir können das verwenden, um unsere Speicherzuweisungen zu verwalten. Was, wenn die Foo-class in ihrem Konstruktor etwas Speicher auf dem Heap zugewiesen hat und diesen Speicher in seinem Destruktor gelöscht hat? Dann könnten wir das Beste aus beiden Welten herausholen, sichere Speicherzuweisungen, die garantiert wieder freigegeben werden, aber ohne die Einschränkungen, alles auf den Stapel zu zwingen.

Und genau so funktioniert der meiste C ++ Code. Sehen Sie sich beispielsweise den std::vector der Standardbibliothek an. Dies wird normalerweise auf dem Stapel zugewiesen, kann aber dynamisch skaliert und in der Größe geändert werden. Und dies geschieht durch internes Zuweisen von Speicher auf dem Heap nach Bedarf. Der Benutzer der class sieht dies nie, daher gibt es keine Möglichkeit, Speicher zu verlieren oder zu vergessen, was Sie zugewiesen haben.

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, databaseverbindungen, Synchronisationssperren). Sie können alle im Konstruktor erworben und im Destruktor veröffentlicht werden, sodass Sie garantiert alle Ressourcen, die Sie erhalten, wieder freigeben können.

Verwenden Sie in der Regel nie new / delete direkt von Ihrem High-Level-Code. Wickeln Sie es immer in eine class ein, die den Speicher für Sie verwalten kann und dafür sorgt, dass es wieder freigegeben wird. (Ja, es kann Ausnahmen von dieser Regel geben. Insbesondere erfordern Smart Pointer, dass Sie new direkt aufrufen und den pointers an seinen Konstruktor übergeben, der dann übernimmt und sicherstellt, dass delete korrekt aufgerufen wird. Aber das ist immer noch eine sehr wichtige Regel Daumen)

Welche Methode sollte ich verwenden?

Dies wird fast nie durch Ihre Tipppräferenzen, sondern durch den Kontext bestimmt. Wenn Sie das Objekt über einige Stapel halten 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 .

Um die Last des Free-Store-Managements zu erleichtern, haben Leute auto_ptr wie auto_ptr und unique_ptr erfunden. Ich empfehle dringend, dass Sie sich diese ansehen. Sie könnten dir bei deinen Tippproblemen sogar helfen 😉

Wenn Sie in C ++ schreiben, schreiben Sie wahrscheinlich für die performance. Die Verwendung von new und dem kostenlosen Speicher ist viel langsamer als die Verwendung des Stacks (insbesondere bei der Verwendung von Threads). Verwenden Sie ihn daher nur dann, wenn Sie ihn benötigen.

Wie andere bereits gesagt haben, müssen Sie neu sein, wenn Ihr Objekt außerhalb der function oder des Objektbereichs leben muss, das Objekt wirklich groß ist oder wenn Sie die Größe eines Arrays zum Zeitpunkt der Kompilierung nicht kennen.

Versuche auch zu vermeiden, jemals delete zu verwenden. Wickeln Sie Ihr neues in einen intelligenten pointers stattdessen ein. Lassen Sie den Smart Pointer-Aufruf für Sie löschen.

Es gibt Fälle, in denen ein intelligenter pointers nicht intelligent ist. Speichern Sie niemals std :: auto_ptr <> in einem STL-Container. Der Zeiger wird aufgrund von Kopiervorgängen innerhalb des Containers 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 <> wird eine Menge Geschwindigkeit Overhead haben, da es die Referenzzählungen auf und ab bewegt. In diesem Fall ist es besser, den STL-Container in ein anderes Objekt zu legen und diesem Objekt einen Destruktor zuzuweisen, der bei jedem pointers im Container den Löschvorgang aufruft.

Die kurze Antwort lautet: Wenn Sie ein Anfänger in C ++ sind, sollten Sie nie new oder sich selbst delete . Stattdessen sollten Sie intelligente pointers wie std::unique_ptr (oder seltener std::shared_ptr ) verwenden. Auf diese Weise müssen Sie sich nicht annähernd so viel Gedanken über Speicherlecks machen. Und selbst wenn Sie fortgeschrittener sind, sollten Sie in der Regel die benutzerdefinierte Methode, mit der Sie new und delete in eine kleine class (z. B. einen benutzerdefinierten Smartpointer) kapseln, die nur Objektlebenszyklusproblemen gewidmet ist.

Hinter den Kulissen führen diese intelligenten pointers natürlich immer noch eine dynamische Zuweisung und Freigabe durch, so dass Code, der sie verwendet, immer noch den zugehörigen Laufzeit-Overhead aufweisen würde. Andere Antworten hier haben diese Probleme behandelt und wie Designentscheidungen darüber getroffen werden können, wann Smartpointer verwendet werden sollen, anstatt nur Objekte auf dem Stack zu erstellen oder sie als direkte Elemente eines Objekts zu integrieren, gut genug, um sie nicht zu wiederholen. Aber meine Zusammenfassung wäre: Verwenden Sie keine intelligenten pointers oder dynamische Zuweisung, bis etwas Sie zwingt.

Ohne das new Schlüsselwort speichern Sie das auf dem Aufruf-Stack . Das Speichern übermäßig großer Variablen auf dem Stapel führt zu einem Stapelüberlauf .

Wenn Ihre Variable nur im Kontext einer einzelnen function verwendet wird, verwenden Sie am besten eine Stapelvariable, dh Option 2. Wie andere bereits gesagt haben, müssen Sie die Lebensdauer von Stapelvariablen nicht verwalten – sie sind konstruiert und zerstört automatisch. Außerdem ist das Zuweisen / Freigeben einer Variablen auf dem Heap im Vergleich langsam. Wenn Ihre function oft genug aufgerufen wird, werden Sie eine enorme performancesverbesserung feststellen, wenn Sie Stack-Variablen im Vergleich zu Heap-Variablen verwenden.

Das heißt, es gibt einige offensichtliche Fälle, in denen Stack-Variablen nicht ausreichen.

Wenn die Stapelvariable einen großen Speicherbedarf hat, besteht die Gefahr, dass der Stapel überläuft. Standardmäßig ist die Stapelgröße jedes Threads unter Windows 1 MB . Es ist unwahrscheinlich, dass Sie eine Stack-Variable erstellen, die 1 MB groß ist, aber Sie müssen daran denken, dass die Stack-Nutzung kumulativ ist. Wenn Ihre function eine function aufruft, die eine andere function aufruft, die eine andere function aufruft, die …, nehmen die Stapelvariablen in all diesen functionen Platz auf dem gleichen Stapel ein. Rekursive functionen können schnell auf dieses Problem stoßen, abhängig davon, wie tief die Rekursion ist. Wenn dies ein Problem ist, können Sie die Größe des Stapels erhöhen (nicht empfohlen) oder die Variable auf dem Heap mithilfe des neuen Operators zuweisen (empfohlen).

Die andere wahrscheinlichere Bedingung ist, dass Ihre Variable über den Rahmen Ihrer function hinaus “leben” muss. In diesem Fall würden Sie die Variable auf dem Heap so zuweisen, dass sie außerhalb des Bereichs einer bestimmten function erreicht werden kann.

Übergeben Sie MyClass aus einer function oder erwarten Sie, dass es außerhalb dieser function existiert? Wie einige andere sagten, dreht sich alles um den Bereich, wenn Sie nicht auf dem Heap reservieren. Wenn Sie die function verlassen, verschwindet sie (eventuell). Einer der klassischen Fehler, die von Anfängern gemacht werden, ist der Versuch, ein lokales Objekt einer class in einer function zu erstellen und es zurückzugeben, ohne es auf dem Heap zuzuweisen. Ich kann mich daran erinnern, dass ich diese Art von Dingen in früheren Tagen mit C ++ debuggen musste.

Die einfache Antwort ist yes – new () erstellt ein Objekt auf dem Heap (mit dem unglücklichen Nebeneffekt, dass Sie seine Lebensdauer verwalten müssen, indem Sie explizit delete aufrufen, während das zweite Formular ein Objekt im Stack im aktuellen erstellt scope und dieses Objekt wird zerstört, wenn es den Rahmen verlässt.

Die kurze Antwort ist ja, das “neue” Schlüsselwort ist unglaublich wichtig, denn wenn Sie es verwenden, werden die Objektdaten auf dem Heap im Gegensatz zum Stack gespeichert, was am wichtigsten ist!

Die zweite Methode erstellt die Instanz auf dem Stapel, zusammen mit Dingen wie int deklariert und die Liste der Parameter, die an die function übergeben werden.

Die erste Methode bietet Platz für einen pointers auf dem Stapel, den Sie auf den Speicherort im Speicher festgelegt haben, wo eine neue MyClass auf dem Heap – oder dem kostenlosen Speicher reserviert wurde.

Die erste Methode erfordert auch, dass Sie delete was Sie mit new erstellen, während in der zweiten Methode die class automatisch zerstört und freigegeben wird, wenn sie außerhalb des Geltungsbereichs liegt (normalerweise die nächste schließende Klammer).