Werden 'neu' und 'löschen' in C ++ veraltet?

68

Ich bin auf ein Quiz gestoßen, das eine Array-Deklaration mit unterschiedlichen Größen beinhaltete. Das erste, was mir in den Sinn kam, war, dass ich mit dem newBefehl eine dynamische Zuordnung verwenden müsste , wie folgt :

while(T--) {
   int N;
   cin >> N;
   int *array = new int[N];
   // Do something with 'array'
   delete[] array;
}

Ich habe jedoch gesehen, dass eine der Lösungen den folgenden Fall zulässt:

while(T--) {
    int N;
    cin >> N;
    int array[N];
    // Do something with 'array'
}

Nach ein wenig Recherche habe ich gelesen, dass g ++ dies zulässt, aber ich habe immer wieder darüber nachgedacht, in welchen Fällen ist es dann notwendig, die dynamische Zuordnung zu verwenden? Oder übersetzt der Compiler dies als dynamische Zuordnung?

Die Löschfunktion ist enthalten. Beachten Sie jedoch, dass es hier nicht um Speicherlecks geht.

learning_dude
quelle
54
Das zweite Beispiel verwendet ein Array mit variabler Länge, das noch nie Teil von C ++ war. Verwenden Sie für diesen Fall std::vectorstattdessen ( std::vector<int> array(N);).
Ein Programmierer
7
Die direkte Antwort auf Ihre Frage sollte lauten: Nein, sie wird nicht veraltet. Obwohl moderne Versionen von C ++ viele Funktionen bieten, die die Verwaltung des Speicherbesitzes vereinfachen (intelligente Zeiger), ist es immer noch üblich, Objekte durch new OBJdirekten Aufruf zuzuweisen .
pptaszni
8
Für andere Leute, die verwirrt sind, warum Leute über Speicherlecks sprechen, wurde die Frage bearbeitet, um einen Fehler zu korrigieren, der für die Frage nicht wesentlich war
Mike Caron
4
@Mannoj bevorzugt die Begriffe Dynamisch und Automatisch, um zu häufen und zu stapeln. Es ist selten, aber es ist möglich, C ++ ohne Haufen und Stapel zu implementieren.
user4581301
1
In C ++ wurde nie etwas veraltet und nichts wird es jemals tun. Das ist ein Teil dessen, was C ++ bedeutet.
JoelFan

Antworten:

114

Keines der gezeigten Snippets ist idiomatischer, moderner C ++ - Code.

newund delete(und new[]und delete[]) sind in C ++ nicht veraltet und werden es niemals sein. Sie sind immer noch die Möglichkeit, dynamisch zugewiesene Objekte zu instanziieren. Da Sie jedoch immer a newmit a delete(und a new[]mit a delete[]) abgleichen müssen, werden sie am besten in (Bibliotheks-) Klassen aufbewahrt, die dies für Sie sicherstellen. Siehe Warum sollten C ++ - Programmierer die Verwendung von "neu" minimieren? .

Ihr erstes Snippet verwendet ein "nacktes" new[]und dann nie delete[]das erstellte Array. Das ist ein Problem. std::vectormacht alles was du brauchst hier ganz gut. Es wird eine Form von newhinter den Kulissen verwendet (ich werde nicht auf Implementierungsdetails eingehen), aber alles, was Sie beachten müssen, ist ein dynamisches Array, aber besser und sicherer.

Ihr zweites Snippet verwendet "Arrays mit variabler Länge" (VLAs), eine C-Funktion, die einige Compiler auch in C ++ als Erweiterung zulassen. Im Gegensatz dazu newwerden VLAs im Wesentlichen auf dem Stapel zugewiesen (eine sehr begrenzte Ressource). Noch wichtiger ist jedoch, dass sie keine Standard-C ++ - Funktion sind und vermieden werden sollten, da sie nicht portierbar sind. Sie ersetzen sicherlich nicht die dynamische (dh Heap-) Zuordnung.

Max Langhof
quelle
3
Ich möchte hinzufügen, dass VLAs zwar offiziell nicht im Standard enthalten sind, aber von allen wichtigen Compilern unterstützt werden. Daher ist die Entscheidung, ob sie vermieden werden sollen oder nicht, eher eine Frage des Stils / der Präferenz als eine realistische Sorge um die Portabilität.
Stack Tracer
4
Denken Sie auch daran, dass Sie ein Array nicht zurückgeben oder an einem anderen Ort speichern können, damit VLA die Ausführungszeit der Funktion nie überlebt
Ruslan
16
@StackTracer Nach meinem besten Wissen unterstützt MSVC keine VLAs. Und MSVC ist definitiv ein "großer Compiler".
Max Langhof
2
"Sie müssen immer eine neue mit einer Löschung abgleichen" - nicht, wenn Sie damit arbeiten Qt, da die Basisklassen alle über Garbage Collectors verfügen. Sie verwenden newsie also meistens und vergessen sie meistens. Wenn für übergeordnete GUI-Elemente das übergeordnete Widget geschlossen wird, verlassen die untergeordneten Elemente den Gültigkeitsbereich und werden automatisch mit dem Müll erfasst.
vsz
6
@vsz Auch in Qt hat jeder newnoch eine Übereinstimmung delete; Es ist nur so, dass die deletes vom übergeordneten Widget ausgeführt werden und nicht im selben Codeblock wie die news.
jjramsey
22

Nun, für Vorspeisen, new/ deletesind nicht veraltet zu werden .

In Ihrem speziellen Fall sind sie jedoch nicht die einzige Lösung. Was Sie auswählen, hängt davon ab, was unter Ihrem Kommentar "Mach etwas mit Array" versteckt wurde.

In Ihrem zweiten Beispiel wird eine nicht standardmäßige VLA-Erweiterung verwendet, die versucht, das Array auf den Stapel zu passen. Dies hat bestimmte Einschränkungen - nämlich eine begrenzte Größe und die Unfähigkeit, diesen Speicher zu verwenden, nachdem das Array den Gültigkeitsbereich verlassen hat. Sie können es nicht herausziehen, es wird "verschwinden", nachdem sich der Stapel abgewickelt hat.

Wenn Ihr einziges Ziel darin besteht, eine lokale Berechnung durchzuführen und die Daten dann wegzuwerfen, funktioniert dies möglicherweise tatsächlich einwandfrei. Ein robusterer Ansatz wäre jedoch, den Speicher dynamisch zuzuweisen, vorzugsweise mit std::vector. Auf diese Weise erhalten Sie die Möglichkeit, Platz für genau so viele Elemente zu schaffen, wie Sie benötigen, basierend auf einem Laufzeitwert (was wir die ganze Zeit anstreben), aber es wird sich auch gut bereinigen und Sie können ihn verschieben dieses Bereichs, wenn Sie den Speicher für später verwenden möchten.

Wenn Sie zum Anfang zurückkehren, vector werden Sie wahrscheinlich newein paar Schichten tiefer gehen, aber Sie sollten sich damit nicht befassen, da die Benutzeroberfläche viel besser ist. In diesem Sinne kann die Verwendung von newund deleteals entmutigt angesehen werden.

Bartek Banachewicz
quelle
1
Beachten Sie die "... einige Schichten tiefer". Wenn Sie Ihre eigenen Container implementieren möchten, sollten Sie dennoch die Verwendung von newund vermeiden delete, sondern intelligente Zeiger wie verwenden std::unique_pointer.
Max
1
das heißt eigentlichstd::unique_ptr
user253751
2
@Max: std::unique_ptrStandard-Destruktoraufrufe deleteoder delete[], was bedeutet, dass das eigene Objekt von newoder auf new[]jeden Fall zugewiesen worden sein muss, in welche Aufrufe std::make_uniqueseit C ++ 14 verborgen wurden .
Laurent LA RIZZA
15

In Ihrem zweiten Beispiel werden Arrays mit variabler Länge (VLAs) verwendet, die eigentlich eine C99-Funktion ( nicht C ++!) Sind, aber dennoch von g ++ unterstützt werden .

Siehe auch diese Antwort .

Beachten Sie, dass Arrays mit variabler Länge sich von new/ unterscheiden deleteund diese in keiner Weise "verwerfen".

Beachten Sie auch, dass VLAs nicht ISO C ++ sind.

andreee
quelle
13

Modernes C ++ bietet einfachere Möglichkeiten, mit dynamischen Zuordnungen zu arbeiten. Intelligente Zeiger können sich um die Bereinigung nach Ausnahmen (die bei Bedarf überall auftreten können) und vorzeitigen Rückgaben kümmern, sobald die referenzierten Datenstrukturen den Gültigkeitsbereich verlassen. Daher kann es sinnvoll sein, diese stattdessen zu verwenden:

  int size=100;

  // This construct requires the matching delete statement.
  auto buffer_old = new int[size];

  // These versions do not require `delete`:
  std::unique_ptr<int[]> buffer_new (new int[size]);
  std::shared_ptr<int[]> buffer_new (new int[size]); 
  std::vector<int> buffer_new (size);  int* raw_access = buffer_new.data();

Ab C ++ 14 können Sie auch schreiben

auto buffer_new = std::make_unique<int[]>(size);

Dies sieht noch besser aus und würde einen Speicherverlust verhindern, wenn die Zuordnung fehlschlägt. Ab C ++ 20 sollten Sie in der Lage sein, so viel wie möglich zu tun

auto a = std::make_shared<int[]>(size);

Dies wird für mich zum Zeitpunkt des Schreibens mit gcc 7.4.0 noch nicht kompiliert. In diesen beiden Beispielen verwenden wir auch autoanstelle der Typdeklaration links. Verwenden Sie in allen Fällen das Array wie gewohnt:

buffer_old[0] = buffer_new[0] = 17;

Speicherlecks newund Abstürze durch Verdoppelung deletesind etwas, das C ++ seit vielen Jahren verprügelt hat und der "zentrale Punkt" der Argumentation für den Wechsel in andere Sprachen ist. Vielleicht besser zu vermeiden.

Audrius Meskauskas
quelle
Sie sollten die unique/shared_ptrKonstruktoren zugunsten von vermeiden make_unique/shared, müssen nicht nur den konstruierten Typ nicht zweimal schreiben (mit auto), sondern Sie riskieren auch nicht, Speicher oder Ressourcen zu verlieren, wenn die Konstruktion teilweise fehlschlägt (wenn Sie einen Typ verwenden, der fehlschlagen kann).
Simon Buchan
2
make_unique ist nur mit Arrays aus C ++ 14 und make_shared aus C ++ 20 verfügbar. Dies ist immer noch selten eine Standardeinstellung, daher hat mich das Vorschlagen von std :: make_shared <int []> (Größe) etwas früher gesucht.
Audrius Meskauskas
Meinetwegen! Ich benutze nicht wirklich make_shared<int[]>viel, wenn du so ziemlich immer willst vector<int>, aber gut zu wissen.
Simon Buchan
Übermäßige Pedanterie, aber IIRC, der unique_ptrKonstruktor ist nothrow, daher Tgibt es nothrow-Konstruktoren, daher besteht kein Risiko für Lecks mit unique_ptr(new int[size])und shared_ptrhat Folgendes: "Wenn eine Ausnahme ausgelöst wird, wird delete p aufgerufen, wenn T kein Array-Typ ist, delete [ ] p sonst. ", so haben Sie den gleichen Effekt - das Risiko ist für unique/shared_ptr(new MyPossiblyAllocatingType[size]).
Simon Buchan
3

Neu und Löschen werden nicht veraltet.

Die vom neuen Operator erstellten Objekte können als Referenz übergeben werden. Die Objekte können mit delete gelöscht werden.

Neu und Löschen sind die grundlegenden Aspekte der Sprache. Die Persistenz eines Objekts kann mit new und delete verwaltet werden. Diese werden definitiv nicht veraltet sein.

Die Anweisung - int array [N] ist eine Möglichkeit, ein Array zu definieren. Das Array kann im Rahmen des umschließenden Codeblocks verwendet werden. Es kann nicht so übergeben werden, wie ein Objekt an eine andere Funktion übergeben wird.

Gopinath
quelle
2

Das erste Beispiel benötigt delete[]am Ende ein, sonst tritt ein Speicherverlust auf.

Im zweiten Beispiel wird eine variable Arraylänge verwendet, die von C ++ nicht unterstützt wird. Es erlaubt nur einen konstanten Ausdruck für die Array-Länge .

In diesem Fall ist es nützlich, std::vector<>als Lösung zu verwenden; Dadurch werden alle Aktionen, die Sie für ein Array ausführen können, in eine Vorlagenklasse zusammengefasst.

Zick-Rasiermesser
quelle
3
Was meinst du mit "bis C ++ 11"? Ich bin mir ziemlich sicher, dass VLAs nie Teil des Standards wurden.
Churill
Schauen Sie sich den Standard von c ++ 14 [c ++ 14 Standard] ( isocpp.org/files/papers/N3690.pdf ) auf Seite 184 an. Absatz 8.3.4
Zick-Rasiermesser
4
Das ist nicht der Standard, sondern nur ein Entwurf und der Teil über "Arrays of Runtime Bound" haben es, soweit ich das beurteilen kann, nicht in den Standard geschafft. Cppreference erwähnt dort keine VLAs.
Churill
1
@zigrazor cppreference.com enthält eine Liste mit Links zu den nächstgelegenen Entwürfen vor / nach der Veröffentlichung der einzelnen Standards. Die veröffentlichten Standards sind nicht frei verfügbar, aber diese Entwürfe sollten sehr eng sein. Wie Sie den Dokumentennummern entnehmen können, handelt es sich bei Ihrem verknüpften Entwurf um einen älteren Arbeitsentwurf für C ++ 14.
Walnuss
2
@learning_dude Es wird nicht von Standards unterstützt. Die Antwort ist (jetzt) ​​richtig (wenn auch kurz). Es funktioniert nur für Sie, weil GCC es als nicht standardmäßige Erweiterung zulässt .
Walnuss
-4

Die Syntax sieht aus wie C ++, aber die Redewendung ähnelt der einfachen alten Algol60. Es war üblich, Codeblöcke wie diesen zu haben:

read n;
begin
    integer array x[1:n];
    ... 
end;

Das Beispiel könnte wie folgt geschrieben werden:

while(T--) {
    int N;
    cin >> N;
    {
        int array[N];
        // Do something with 'array'
    }
}

Ich vermisse das manchmal in den aktuellen Sprachen;)

kdo
quelle