360 Stimmen

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

Ich verwende C++ erst seit kurzem und habe mich über die new Stichwort. Sollte ich es einfach verwenden oder nicht?

  1. Mit dem new Stichwort...

    MyClass* myClass = new MyClass();
    myClass->MyField = "Hello world!";
  2. Ohne die new Stichwort...

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

Aus der Perspektive der Implementierung scheinen sie sich nicht so sehr zu unterscheiden (aber ich bin sicher, dass sie es tun)... Allerdings ist meine primäre Sprache C#, und natürlich die erste Methode ist, was ich gewohnt bin.

Die Schwierigkeit scheint darin zu bestehen, dass Methode 1 mit den Standard-C++-Klassen schwieriger zu verwenden ist.

Welche Methode sollte ich anwenden?

Aktualisierung 1:

Ich habe vor kurzem die new Schlüsselwort für Haufen Speicher (oder freies Lager ) für ein großes Array, das den Anwendungsbereich verlässt (d.h. von einer Funktion zurückgegeben wird). Während ich zuvor den Stack verwendet hatte, was dazu führte, dass die Hälfte der Elemente außerhalb des Bereichs beschädigt wurde, konnte ich durch den Wechsel zur Heap-Nutzung sicherstellen, dass die Elemente intakt waren. Juhu!

Update 2:

Ein Freund von mir hat mir kürzlich erzählt, dass es eine einfache Regel für die Verwendung der new Schlüsselwort; jedes Mal, wenn Sie new , Typ 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 unterbringen müssen (z. B. beim Ausschneiden und Einfügen in einen Destruktor oder anderweitig).

381voto

Daniel LeCheminant Punkte 49305

Methode 1 (mit new )

  • Weist Speicher für das Objekt auf der Registerkarte freies Lager (Dies ist häufig dasselbe wie die Haufen )
  • Erfordert, dass Sie explizit delete Ihr Objekt später. (Wenn Sie es nicht löschen, könnten Sie ein Speicherleck verursachen)
  • Der Speicher bleibt zugewiesen, bis Sie delete es. (d.h. Sie könnten return ein Objekt, das Sie mit new )
  • Das Beispiel in der Frage wird Speicher lecken es sei denn, der Zeiger ist delete d; und es sollten immer gelöscht werden unabhängig davon, welcher Kontrollpfad genommen wird oder ob Ausnahmen ausgelöst werden.

Methode 2 (ohne Verwendung von new )

  • Weist Speicher für das Objekt auf der Registerkarte Stapel (wo alle lokalen Variablen hingehen) Es ist im Allgemeinen weniger Speicher für den Stack verfügbar; wenn Sie zu viele Objekte zuweisen, riskieren Sie einen Stack-Überlauf.
  • Das brauchen Sie nicht delete es später.
  • Speicher wird nicht mehr zugewiesen, wenn er den Geltungsbereich verlässt. (d.h. Sie sollten nicht return ein Zeiger auf ein Objekt auf dem Stack)

Sie können die Methode wählen, die für Sie unter den oben genannten Bedingungen am besten geeignet ist.

Einige einfache Fälle:

  • Wenn Sie sich nicht um den Anruf kümmern wollen delete (und das Potenzial, zu Speicherlecks ) sollten Sie nicht verwenden new .
  • Wenn Sie einen Zeiger auf Ihr Objekt aus einer Funktion zurückgeben möchten, müssen Sie new

134voto

jalf Punkte 235501

Es gibt einen wichtigen Unterschied zwischen diesen beiden.

Alles, was nicht mit new verhält sich ähnlich wie Werttypen in C# (und es wird oft gesagt, dass diese Objekte auf dem Stack zugewiesen werden, was wahrscheinlich der häufigste/offensichtlichste Fall ist, aber nicht immer stimmt). Genauer gesagt, Objekte, die ohne Verwendung von new haben automatische Speicherdauer Alles zugeordnet mit new wird auf dem Heap alloziert und ein Zeiger darauf zurückgegeben, genau wie Referenztypen in C#.

Alles, was auf dem Stack zugewiesen wird, muss eine konstante Größe haben, die zur Kompilierzeit bestimmt wird (der Compiler muss den Stack-Zeiger 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. Das müssen sie auch sein, denn bei Referenztypen können wir zur Laufzeit entscheiden, wie viel Speicher wir anfordern wollen. Und das Gleiche 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 Stack) zugewiesen werden. Arrays mit dynamischer Größe müssen auf dem Heap zugewiesen werden, indem man new .

(Und da hört jede Ähnlichkeit mit C# auf)

Jetzt hat alles, was auf dem Stapel zugewiesen wird, eine "automatische" Speicherdauer (Sie können eine Variable tatsächlich als auto Dies ist jedoch die Standardeinstellung, wenn kein anderer Speichertyp angegeben ist, so dass das Schlüsselwort in der Praxis nicht wirklich verwendet wird, aber daher stammt es)

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

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

Diese Funktion erzeugt drei Werte, die es zu berücksichtigen gilt:

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

In Zeile 2 deklariert er eine bar Zeiger b2 auf dem Stapel (automatische Dauer), et ruft new auf und weist eine bar Objekt auf dem Heap. (dynamische Dauer)

Wenn die Funktion zurückkehrt, geschieht Folgendes: Erstens, b2 aus dem Rahmen fällt (die Reihenfolge der Zerstörung ist immer entgegengesetzt zur Reihenfolge des Aufbaus). Aber b2 ist nur ein Zeiger, es passiert also nichts, der Speicher, den er belegt, wird einfach freigegeben. Und wichtig ist, dass der Speicher, der zeigt auf (die bar Instanz auf dem Heap) wird NICHT berührt. Nur der Zeiger wird freigegeben, da nur der Zeiger eine automatische Dauer hatte. Zweitens, b den Anwendungsbereich verlässt, wird sein Destruktor aufgerufen und der Speicher freigegeben, da er eine automatische Dauer hat.

Und die bar Instanz auf dem Heap? Sie ist wahrscheinlich noch da. Niemand hat sich die Mühe gemacht, sie zu löschen, also haben wir Speicher verloren.

Aus diesem Beispiel geht hervor, dass alles mit automatischer Dauer garantiert dass sein Destruktor aufgerufen wird, wenn er den Anwendungsbereich verlässt. Das ist nützlich. Aber alles, was auf dem Heap alloziert wird, hält so lange, wie wir es brauchen, und kann dynamisch dimensioniert werden, wie im Fall von Arrays. Auch das ist nützlich. Wir können dies nutzen, um unsere Speicherzuweisungen zu verwalten. Was wäre, wenn die Klasse Foo in ihrem Konstruktor Speicher auf dem Heap zuweisen und diesen Speicher in ihrem Destruktor wieder löschen würde? Dann hätten wir das Beste aus beiden Welten: sichere Speicherzuweisungen, die garantiert wieder freigegeben werden, aber ohne die Einschränkungen, die dadurch entstehen, dass alles auf dem Stack liegen muss.

Und genau so funktioniert der meiste C++-Code. Schauen Sie sich die Standardbibliothek von std::vector zum Beispiel. Dieser wird normalerweise auf dem Stack zugewiesen, kann aber dynamisch in der Größe verändert werden. Dies geschieht durch die interne Zuweisung von Speicher auf dem Heap nach Bedarf. Der Benutzer der Klasse sieht dies nie, so dass es keine Möglichkeit gibt, Speicher zu verlieren oder zu vergessen, den zugewiesenen Speicher aufzuräumen.

Dieses Prinzip wird RAII (Resource Acquisition is Initialization) genannt und kann auf jede Ressource ausgedehnt werden, die erworben und freigegeben werden muss. (Netzwerksockel, Dateien, Datenbankverbindungen, Synchronisationssperren). Alle diese Ressourcen können im Konstruktor erworben und im Destruktor freigegeben werden, so dass gewährleistet ist, dass alle erworbenen Ressourcen wieder freigegeben werden.

Generell sollten Sie new/delete nie direkt in Ihrem High-Level-Code verwenden. 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 erfordern intelligente Zeiger den Aufruf von new direkt, und übergeben Sie den Zeiger an seinen Konstruktor, der dann die Aufgabe übernimmt und dafür sorgt, dass delete korrekt aufgerufen wird. Aber dies ist immer noch eine sehr wichtige Faustregel)

25voto

Daniel Schepler Punkte 2873

Die kurze Antwort lautet: Wenn Sie ein Anfänger in C++ sind, sollten Sie niemals verwenden new o delete selbst.

Stattdessen sollten Sie intelligente Zeiger verwenden, wie std::unique_ptr et std::make_unique (oder weniger oft, std::shared_ptr et std::make_shared ). Auf diese Weise müssen Sie sich nicht so viele Gedanken über Speicherlecks machen. Und selbst wenn Sie fortgeschrittener sind, wäre es in der Regel die beste Praxis, die benutzerdefinierte Art und Weise, wie Sie die new et delete in eine kleine Klasse (z. B. einen benutzerdefinierten Smart Pointer), die sich ausschließlich mit Fragen des Objektlebenszyklus befasst.

Natürlich führen diese intelligenten Zeiger hinter den Kulissen immer noch eine dynamische Zuweisung und Freigabe durch, so dass der Code, der sie verwendet, immer noch den damit verbundenen Laufzeit-Overhead hat. In anderen Antworten hier wurden diese Probleme und die Frage, wie man Design-Entscheidungen über die Verwendung von Smart-Pointern im Vergleich zur einfachen Erstellung von Objekten auf dem Stack oder deren Einbindung als direkte Mitglieder eines Objekts trifft, so gut behandelt, dass ich sie nicht wiederholen möchte. Aber meine Zusammenfassung wäre: Verwenden Sie keine Smart-Pointer oder dynamische Zuweisung, bis etwas Sie dazu zwingt.

14voto

dirkgently Punkte 104289

Welche Methode sollte ich anwenden?

Das hängt fast nie von Ihren Schreibpräferenzen ab, sondern vom Kontext. Wenn Sie das Objekt über mehrere Stapel hinweg behalten müssen oder wenn es zu schwer für den Stapel ist, weisen Sie es dem freien Speicher zu. Da Sie ein Objekt zuweisen, sind Sie auch für die Freigabe des Speichers verantwortlich. Schauen Sie im delete Betreiber.

Um den Aufwand für die Verwaltung freier Speicher zu verringern, haben Menschen Dinge erfunden wie auto_ptr et unique_ptr . Ich empfehle Ihnen dringend, einen Blick darauf zu werfen. Vielleicht helfen sie Ihnen sogar bei Ihren Schreibproblemen ;-)

12voto

Zan Lynx Punkte 51045

Wenn Sie in C++ schreiben, sind Sie wahrscheinlich auf Leistung aus. Die Verwendung von new und dem freien Speicher ist viel langsamer als die Verwendung des Stacks (insbesondere bei der Verwendung von Threads), also verwenden Sie ihn nur, wenn Sie ihn brauchen.

Wie bereits gesagt, benötigen Sie new, wenn Ihr Objekt außerhalb des Funktions- oder Objektbereichs liegen muss, wenn das Objekt sehr groß ist oder wenn Sie die Größe eines Arrays zur Kompilierzeit nicht kennen.

Versuchen Sie auch, die Funktion "Löschen" zu vermeiden. Wickeln Sie Ihre neue in einen intelligenten Zeiger statt. Lassen Sie den Smart Pointer delete für Sie aufrufen.

Es gibt einige Fälle, in denen ein intelligenter Zeiger nicht intelligent ist. Speichern Sie niemals std::auto_ptr<> innerhalb eines STL-Containers. Er wird den Zeiger zu früh löschen, weil Kopieroperationen innerhalb des Containers stattfinden. Ein anderer Fall ist, wenn Sie einen wirklich großen STL-Container mit Zeigern auf Objekte haben. boost::shared_ptr<> wird einen enormen Geschwindigkeits-Overhead haben, da es die Anzahl der Referenzen hoch und runter schiebt. In diesem Fall ist es besser, den STL-Container in ein anderes Objekt zu packen und diesem Objekt einen Destruktor zu geben, der jeden Zeiger im Container mit delete belegt.

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