Es gibt zwei weit verbreitete Speicherzuweisungstechniken: automatische Zuweisung und dynamische Zuweisung. Üblicherweise gibt es für jeden einen entsprechenden Speicherbereich: den Stapel und den Heap.
Stapel
Der Stapel weist den Speicher immer sequentiell zu. Dies ist möglich, da Sie den Speicher in umgekehrter Reihenfolge freigeben müssen (First-In, Last-Out: FILO). Dies ist die Speicherzuweisungstechnik für lokale Variablen in vielen Programmiersprachen. Es ist sehr, sehr schnell, da es nur minimale Buchhaltung erfordert und die nächste zuzuweisende Adresse implizit ist.
In C ++ wird dies als automatischer Speicher bezeichnet, da der Speicher am Ende des Bereichs automatisch beansprucht wird. Sobald die Ausführung des aktuellen Codeblocks (begrenzt durch {}
) abgeschlossen ist, wird automatisch Speicher für alle Variablen in diesem Block gesammelt. Dies ist auch der Moment, in dem Destruktoren aufgerufen werden, um Ressourcen zu bereinigen.
Haufen
Der Heap ermöglicht einen flexibleren Speicherzuweisungsmodus. Die Buchhaltung ist komplexer und die Zuordnung ist langsamer. Da es keinen impliziten Freigabepunkt gibt, müssen Sie den Speicher manuell mit delete
oder delete[]
( free
in C) freigeben . Das Fehlen eines impliziten Freigabepunkts ist jedoch der Schlüssel zur Flexibilität des Heaps.
Gründe für die Verwendung der dynamischen Zuordnung
Selbst wenn die Verwendung des Heaps langsamer ist und möglicherweise zu Speicherverlusten oder Speicherfragmentierung führt, gibt es sehr gute Anwendungsfälle für die dynamische Zuordnung, da diese weniger begrenzt ist.
Zwei Hauptgründe für die Verwendung der dynamischen Zuordnung:
Sie wissen nicht, wie viel Speicher Sie zur Kompilierungszeit benötigen. Wenn Sie beispielsweise eine Textdatei in eine Zeichenfolge einlesen, wissen Sie normalerweise nicht, welche Größe die Datei hat. Daher können Sie erst entscheiden, wie viel Speicher zugewiesen werden soll, wenn Sie das Programm ausführen.
Sie möchten Speicher zuweisen, der nach dem Verlassen des aktuellen Blocks bestehen bleibt. Beispielsweise möchten Sie möglicherweise eine Funktion schreiben string readfile(string path)
, die den Inhalt einer Datei zurückgibt. In diesem Fall könnten Sie, selbst wenn der Stapel den gesamten Dateiinhalt enthalten könnte, nicht von einer Funktion zurückkehren und den zugewiesenen Speicherblock beibehalten.
Warum eine dynamische Zuordnung oft nicht erforderlich ist
In C ++ gibt es ein ordentliches Konstrukt, das als Destruktor bezeichnet wird . Mit diesem Mechanismus können Sie Ressourcen verwalten, indem Sie die Lebensdauer der Ressource an der Lebensdauer einer Variablen ausrichten. Diese Technik heißt RAII und ist das Unterscheidungsmerkmal von C ++. Es "verpackt" Ressourcen in Objekte. std::string
ist ein perfektes Beispiel. Dieser Ausschnitt:
int main ( int argc, char* argv[] )
{
std::string program(argv[0]);
}
weist tatsächlich eine variable Menge an Speicher zu. Das std::string
Objekt reserviert Speicher mithilfe des Heaps und gibt ihn in seinem Destruktor frei. In diesem Fall haben Sie nicht brauchen , um manuell alle Ressourcen verwalten und immer noch die Vorteile der dynamischen Speicherzuweisung bekam.
Insbesondere impliziert dies, dass in diesem Ausschnitt:
int main ( int argc, char* argv[] )
{
std::string * program = new std::string(argv[0]); // Bad!
delete program;
}
Es ist keine dynamische Speicherzuweisung erforderlich. Das Programm erfordert mehr Eingabe (!) Und birgt das Risiko, die Speicherfreigabe zu vergessen. Dies geschieht ohne offensichtlichen Nutzen.
Warum Sie die automatische Speicherung so oft wie möglich verwenden sollten
Grundsätzlich fasst der letzte Absatz es zusammen. Wenn Sie den automatischen Speicher so oft wie möglich verwenden, werden Ihre Programme:
- schneller zu tippen;
- schneller beim Laufen;
- weniger anfällig für Speicher- / Ressourcenlecks.
Bonuspunkte
In der genannten Frage gibt es zusätzliche Bedenken. Insbesondere die folgende Klasse:
class Line {
public:
Line();
~Line();
std::string* mString;
};
Line::Line() {
mString = new std::string("foo_bar");
}
Line::~Line() {
delete mString;
}
Ist tatsächlich viel riskanter als die folgende:
class Line {
public:
Line();
std::string mString;
};
Line::Line() {
mString = "foo_bar";
// note: there is a cleaner way to write this.
}
Der Grund ist, dass std::string
ein Kopierkonstruktor richtig definiert wird. Betrachten Sie das folgende Programm:
int main ()
{
Line l1;
Line l2 = l1;
}
Bei Verwendung der Originalversion stürzt dieses Programm wahrscheinlich ab, da es delete
zweimal dieselbe Zeichenfolge verwendet. Unter Verwendung der modifizierten Version, jede Line
Instanz eine eigene Zeichenfolge besitzen Instanz , jedes mit seinem eigenen Speicher und beide werden am Ende des Programms freigegeben.
Weitere Hinweise
Die umfassende Verwendung von RAII wird aus all den oben genannten Gründen als bewährte Methode in C ++ angesehen. Es gibt jedoch einen zusätzlichen Vorteil, der nicht sofort offensichtlich ist. Grundsätzlich ist es besser als die Summe seiner Teile. Der ganze Mechanismus setzt sich zusammen . Es skaliert.
Wenn Sie die Line
Klasse als Baustein verwenden:
class Table
{
Line borders[4];
};
Dann
int main ()
{
Table table;
}
Ordnet vier std::string
Instanzen, vier Line
Instanzen, eine Table
Instanz und den gesamten Inhalt der Zeichenfolge zu, und alles wird automatisch freigegeben .
Monster
spuckt ein a ausTreasure
,World
wenn es stirbt. In seinerDie()
Methode fügt es der Welt den Schatz hinzu. Es mussworld->Add(new Treasure(/*...*/))
in anderen verwendet werden, um den Schatz zu bewahren, nachdem er gestorben ist. Die Alternativen sindshared_ptr
(möglicherweise übertrieben)auto_ptr
(schlechte Semantik für die Übertragung des Eigentums), Wertübergabe (verschwenderisch) undmove
+unique_ptr
(noch nicht weit verbreitet).Weil der Stapel schneller und dicht ist
In C ++ ist nur eine einzige Anweisung erforderlich, um Speicherplatz auf dem Stapel für jedes lokale Bereichsobjekt in einer bestimmten Funktion zuzuweisen, und es ist unmöglich, einen dieser Speicher zu verlieren. Dieser Kommentar wollte (oder hätte beabsichtigen sollen) etwas wie "benutze den Stapel und nicht den Haufen" sagen .
quelle
int x; return &x;
Der Grund dafür ist kompliziert.
Erstens wird in C ++ kein Müll gesammelt. Daher muss für jedes neue ein entsprechendes Löschen vorhanden sein. Wenn Sie diesen Löschvorgang nicht ausführen, liegt ein Speicherverlust vor. Nun zu einem einfachen Fall wie diesem:
Das ist einfach. Aber was passiert, wenn "Do stuff" eine Ausnahme auslöst? Ups: Speicherverlust. Was passiert, wenn "Do stuff"
return
frühzeitig auftritt ? Ups: Speicherverlust.Und das ist für den einfachsten Fall . Wenn Sie diese Zeichenfolge an jemanden zurückgeben, muss dieser sie jetzt löschen. Und wenn sie es als Argument übergeben, muss die Person, die es erhält, es löschen? Wann sollten sie es löschen?
Oder Sie können einfach Folgendes tun:
Nein
delete
. Das Objekt wurde auf dem "Stapel" erstellt und wird zerstört, sobald es den Gültigkeitsbereich verlässt. Sie können das Objekt sogar zurückgeben und so seinen Inhalt an die aufrufende Funktion übertragen. Sie können das Objekt an Funktionen übergeben (normalerweise als Referenz oder Konstantenreferenz :void SomeFunc(std::string &iCanModifyThis, const std::string &iCantModifyThis)
. Und so weiter.Alles ohne
new
unddelete
. Es ist keine Frage, wem der Speicher gehört oder wer für das Löschen verantwortlich ist. Wenn Sie tun:Es versteht sich, dass
otherString
eine Kopie der Daten von hatsomeString
. Es ist kein Zeiger; es ist ein separates Objekt. Sie haben möglicherweise denselben Inhalt, aber Sie können einen ändern, ohne den anderen zu beeinflussen:Sehen Sie die Idee?
quelle
main()
, für die Dauer des Programms vorhanden ist, aufgrund der Situation nicht einfach auf dem Stapel erstellt werden kann und Zeiger darauf an alle Funktionen übergeben werden, die Zugriff darauf benötigen Kann dies bei einem Programmabsturz zu einem Leck führen oder ist es sicher? Ich würde letzteres annehmen, da das Betriebssystem, das den gesamten Speicher des Programms freigibt, es auch logisch freigeben sollte, aber ich möchte nichts annehmen, wenn es darum gehtnew
.Objekte, die von erstellt wurden,
new
müssen möglicherweise entfernt werden,delete
damit sie nicht auslaufen. Der Destruktor wird nicht aufgerufen, der Speicher wird nicht freigegeben, das ganze Bit. Da C ++ keine Garbage Collection hat, ist dies ein Problem.Durch Wert erstellte Objekte (dh auf Stapel) sterben automatisch ab, wenn sie den Gültigkeitsbereich verlassen. Der Destruktoraufruf wird vom Compiler eingefügt und der Speicher wird bei Funktionsrückgabe automatisch freigegeben.
Intelligente Zeiger mögen
unique_ptr
,shared_ptr
lösen das baumelnde Referenz Problem, aber sie erfordern Codierung Disziplin und haben andere potenzielle Probleme (Kopierbarkeit, Referenz Schleifen, etc.).In stark multithreaded Szenarien gibt
new
es auch einen Streitpunkt zwischen Threads. Eine Überbeanspruchung kann sich auf die Leistung auswirkennew
. Die Erstellung von Stapelobjekten erfolgt per Definition threadlokal, da jeder Thread seinen eigenen Stapel hat.Der Nachteil von Wertobjekten besteht darin, dass sie nach der Rückkehr der Hostfunktion sterben. Sie können dem Aufrufer keinen Verweis auf diese Objekte zurückgeben, indem Sie sie nur kopieren, zurückgeben oder nach Wert verschieben.
quelle
new
müssen möglicherweise entfernt werden,delete
damit sie nicht auslaufen." - Schlimmer noch,new[]
muss mit übereinstimmendelete[]
, und Sie erhalten undefiniertes Verhalten, wenn Siedelete
new[]
Speicher oderdelete[]
new
Speicher haben - nur sehr wenige Compiler warnen davor (einige Tools wie Cppcheck tun dies, wenn sie können).quelle
malloc()
oder seine Freunde sendet, um den erforderlichen Speicher zuzuweisen. Der Stapel kann jedoch kein Element innerhalb des Stapels freigeben. Die einzige Möglichkeit, wie der Stapelspeicher jemals freigegeben wird, besteht darin, sich von der Oberseite des Stapels abzuwickeln.Ich sehe, dass einige wichtige Gründe, so wenig Neues wie möglich zu machen, übersehen werden:
Der Operator
new
hat eine nicht deterministische AusführungszeitDas Aufrufen
new
kann dazu führen, dass das Betriebssystem Ihrem Prozess eine neue physische Seite zuweist oder nicht. Dies kann sehr langsam sein, wenn Sie dies häufig tun. Oder es ist bereits ein geeigneter Speicherort bereit, wir wissen es nicht. Wenn Ihr Programm eine konsistente und vorhersehbare Ausführungszeit haben muss (wie in einem Echtzeitsystem oder einer Spiel- / Physiksimulation), müssen Sienew
in Ihrer Zeit kritische Schleifen vermeiden .Der Operator
new
ist eine implizite Thread-SynchronisationJa, Sie haben mich gehört, Ihr Betriebssystem muss sicherstellen, dass Ihre Seitentabellen konsistent sind. Daher führt ein Aufruf
new
dazu, dass Ihr Thread eine implizite Mutex-Sperre erhält. Wenn Sie ständignew
von vielen Threads aus aufrufen, serialisieren Sie tatsächlich Ihre Threads (ich habe dies mit 32 CPUs getan, von denen jedenew
ein paar hundert Bytes abruft, autsch! Das war eine königliche Pita zum Debuggen).Der Rest wie langsam, fragmentiert, fehleranfällig usw. wurde bereits in anderen Antworten erwähnt.
quelle
void * someAddress = ...; delete (T*)someAddress
mlock()
oder etwas Ähnliches. Dies liegt daran, dass dem System möglicherweise der Arbeitsspeicher ausgeht und keine verfügbaren physischen Speicherseiten für den Stapel verfügbar sind. Daher muss das Betriebssystem möglicherweise einige Caches (Clear Dirty Memory) austauschen oder auf die Festplatte schreiben, bevor die Ausführung fortgesetzt werden kann.Pre-C ++ 17:
Weil es zu subtilen Lecks neigt, selbst wenn Sie das Ergebnis in einen intelligenten Zeiger einschließen .
Stellen Sie sich einen "vorsichtigen" Benutzer vor, der sich daran erinnert, Objekte in intelligente Zeiger zu verpacken:
Dieser Code ist gefährlich , weil es gibt keine Garantie , dass entweder
shared_ptr
aufgebaut ist , bevor entwederT1
oderT2
. Wenn einer der beiden erfolgreich istnew T1()
odernew T2()
nach dem anderen fehlschlägt, wird das erste Objekt durchgesickert, da kein Objektshared_ptr
vorhanden ist, das es zerstören und freigeben kann.Lösung: verwenden
make_shared
.Post-C ++ 17:
Dies ist kein Problem mehr: C ++ 17 legt eine Einschränkung für die Reihenfolge dieser Operationen fest. In diesem Fall wird sichergestellt, dass auf jeden Aufruf vonnew()
sofort die Konstruktion des entsprechenden intelligenten Zeigers folgen muss, ohne dass eine andere Operation dazwischen liegt. Dies bedeutet, dass zum Zeitpunkt desnew()
Aufrufs des zweiten Objekts garantiert ist, dass das erste Objekt bereits in seinen intelligenten Zeiger eingeschlossen wurde, wodurch Lecks im Falle einer Ausnahme verhindert werden.Eine detailliertere Erläuterung der neuen Bewertungsreihenfolge, die durch C ++ 17 eingeführt wurde, wurde von Barry in einer anderen Antwort bereitgestellt .Vielen Dank an @Remy Lebeau für den Hinweis, dass dies unter C ++ 17 immer noch ein Problem ist (wenn auch weniger): Der
shared_ptr
Konstruktor kann seinen Steuerblock und seinen Wurf möglicherweise nicht zuordnen. In diesem Fall wird der an ihn übergebene Zeiger nicht gelöscht.Lösung: verwenden
make_shared
.quelle
new
erfolgreich ist und die nachfolgendeshared_ptr
Konstruktion fehlschlägt.std::make_shared()
würde das auch lösenshared_ptr
Konstruktor reserviert Speicher für einen Steuerblock, in dem der gemeinsam genutzte Zeiger gespeichert und gelöscht wird. Ja, theoretisch kann ein Speicherfehler ausgelöst werden. Nur die Konstruktoren zum Kopieren, Verschieben und Aliasing werden nicht ausgelöst.make_shared
ordnet das gemeinsam genutzte Objekt innerhalb des Steuerblocks selbst zu, so dass es nur 1 statt 2 gibt.Zu einem großen Teil ist das jemand, der seine eigenen Schwächen zu einer allgemeinen Regel macht. Es ist nichts falsch an sich mit Objekten Erstellen der Verwendung von
new
Operator. Es gibt einige Argumente dafür, dass Sie dies mit einiger Disziplin tun müssen: Wenn Sie ein Objekt erstellen, müssen Sie sicherstellen, dass es zerstört wird.Der einfachste Weg, dies zu tun, besteht darin, das Objekt im automatischen Speicher zu erstellen, damit C ++ es zerstören kann, wenn es außerhalb des Gültigkeitsbereichs liegt:
Beachten Sie nun, dass der Fall
foo
außerhalb des Bereichs liegt , wenn Sie nach der Endstrebe von diesem Block fallen . C ++ ruft seinen dtor automatisch für Sie auf. Im Gegensatz zu Java müssen Sie nicht warten, bis der GC ihn gefunden hat.Hattest du geschrieben?
Sie möchten es explizit mit abgleichen
oder noch besser, weisen Sie Ihren
File *
als "intelligenten Zeiger" zu. Wenn Sie nicht vorsichtig sind, kann dies zu Undichtigkeiten führen.Die Antwort selbst macht die falsche Annahme, dass Sie, wenn Sie nicht verwenden
new
, nicht auf dem Heap zuordnen; In C ++ wissen Sie das nicht. Sie wissen höchstens, dass eine kleine Menge an Speicher, beispielsweise ein Zeiger, auf dem Stapel zugeordnet ist. Überlegen Sie jedoch, ob die Implementierung von File so etwas wie istdann
FileImpl
wird noch auf dem Stapel zugeordnet.Und ja, Sie sollten es besser haben
auch in der Klasse; Ohne sie verlieren Sie Speicher aus dem Heap, auch wenn Sie den Heap anscheinend überhaupt nicht zugeordnet haben.
quelle
new
per se nichts Falsches ist , aber wenn Sie sich den Originalcode ansehen, auf den sich der Kommentar bezog,new
wird er missbraucht. Der Code ist wie Java oder C # geschrieben, wonew
er für praktisch jede Variable verwendet wird, wenn es viel sinnvoller ist, sich auf dem Stapel zu befinden.new
. Er sagt , dass , wenn Sie haben die Wahl zwischen dem dynamischen Zuordnung und automatischer Speicherung, automatische Speicherung verwenden.new
, aber wenn Sie verwendendelete
, machen Sie es falsch!new()
sollte nicht so wenig wie möglich verwendet werden. Es sollte so vorsichtig wie möglich verwendet werden. Und es sollte so oft verwendet werden, wie es der Pragmatismus vorschreibt.Die Zuordnung von Objekten auf dem Stapel unter Berücksichtigung ihrer impliziten Zerstörung ist ein einfaches Modell. Wenn der erforderliche Bereich eines Objekts zu diesem Modell passt, muss es nicht verwendet werden
new()
, dadelete()
NULL-Zeiger zugeordnet und überprüft werden. In dem Fall, in dem Sie viele kurzlebige Objekte auf dem Stapel haben, sollten die Probleme der Heap-Fragmentierung verringert werden.Wenn die Lebensdauer Ihres Objekts jedoch über den aktuellen Bereich hinausgehen muss,
new()
ist dies die richtige Antwort.delete()
Stellen Sie einfach sicher, dass Sie darauf achten, wann und wie Sie aufrufen und welche Möglichkeiten NULL-Zeiger bieten. Verwenden Sie dazu gelöschte Objekte und alle anderen Fallstricke, die mit der Verwendung von Zeigern einhergehen.quelle
const
oder Zeiger akzeptieren ...?make_shared/_unique
ist verwendbar) der Angerufene niemalsnew
oderdelete
. Diese Antwort verfehlt die eigentlichen Punkte: (A) C ++ bietet Dinge wie RVO, Verschiebungssemantik und Ausgabeparameter - was häufig bedeutet, dass die Behandlung von Objekterstellung und Lebensdauerverlängerung durch Rückgabe von dynamisch zugewiesenem Speicher unnötig und nachlässig wird. (B) Selbst in Situationen, in denen eine dynamische Zuordnung erforderlich ist, bietet die stdlib RAII-Wrapper, die den Benutzer von den hässlichen inneren Details befreien.Wenn Sie new verwenden, werden Objekte dem Heap zugewiesen. Es wird im Allgemeinen verwendet, wenn Sie eine Erweiterung erwarten. Wenn Sie ein Objekt wie z.
es wird auf den Stapel gelegt.
Sie müssen immer destroy für das Objekt aufrufen, das Sie mit new auf den Heap gelegt haben. Dies eröffnet die Möglichkeit von Speicherlecks. Auf dem Stapel platzierte Objekte sind nicht anfällig für Speicherverluste!
quelle
std::string
oderstd::map
, ja, scharfen Einblick. Meine anfängliche Reaktion war "aber auch sehr häufig, um die Lebensdauer eines Objekts vom Gültigkeitsbereich des Erstellungscodes zu entkoppeln", aber esconst
ist besser , wirklich nach Wert zurückzukehren oder aufruferbezogene Werte durch Nichtreferenz oder Zeiger zu akzeptieren , außer wenn es sich um eine "Erweiterung" handelt zu. Es gibt einige andere Sound-Anwendungen wie Factory-Methoden ...Ein bemerkenswerter Grund, um eine Überbeanspruchung des Heapspeichers zu vermeiden, ist die Leistung - insbesondere die Leistung des von C ++ verwendeten Standard-Speicherverwaltungsmechanismus. Während die Zuweisung im trivialen Fall recht schnell sein kann, führt das Ausführen vieler
new
unddelete
ungleichmäßiger Objekte ohne strenge Reihenfolge nicht nur zu einer Speicherfragmentierung, sondern kompliziert auch den Zuweisungsalgorithmus und kann in bestimmten Fällen die Leistung absolut zerstören.Dies ist das Problem, für dessen Lösung Speicherpools erstellt wurden, um die inhärenten Nachteile herkömmlicher Heap-Implementierungen zu verringern und den Heap bei Bedarf weiterhin zu verwenden.
Besser noch, um das Problem insgesamt zu vermeiden. Wenn Sie es auf den Stapel legen können, tun Sie dies.
quelle
Ich denke, das Plakat sollte
You do not have to allocate everything on the
heap
eher sagen als dasstack
.Grundsätzlich werden Objekte auf dem Stapel zugewiesen (sofern die Objektgröße dies zulässt), und zwar aufgrund der günstigen Kosten für die Stapelzuweisung anstelle einer Heap-basierten Zuweisung, die einige Arbeit des Zuweisers erfordert und die Ausführlichkeit erhöht, da dies erforderlich ist Verwalten der auf dem Heap zugewiesenen Daten.
quelle
Ich neige dazu, der Idee, neues "zu viel" zu verwenden, nicht zuzustimmen. Obwohl die Verwendung von Neu mit Systemklassen durch das Originalplakat etwas lächerlich ist. (
int *i; i = new int[9999];
? Wirklich?int i[9999];
ist viel klarer.) Ich denke , das hat die Ziege des Kommentators bekommen.Wenn Sie mit Systemobjekten arbeiten, benötigen Sie sehr selten mehr als einen Verweis auf genau dasselbe Objekt. Solange der Wert gleich ist, ist das alles, was zählt. Und Systemobjekte nehmen normalerweise nicht viel Speicherplatz ein. (ein Byte pro Zeichen in einer Zeichenfolge). In diesem Fall sollten die Bibliotheken so konzipiert sein, dass diese Speicherverwaltung berücksichtigt wird (sofern sie gut geschrieben sind). In diesen Fällen (alle bis auf ein oder zwei Nachrichten in seinem Code) ist Neu praktisch sinnlos und dient nur dazu, Verwirrung und Fehlerpotentiale zu verursachen.
Wenn Sie jedoch mit Ihren eigenen Klassen / Objekten arbeiten (z. B. der Line-Klasse des Originalplakats), müssen Sie selbst über Probleme wie Speicherbedarf, Persistenz von Daten usw. nachdenken. Zu diesem Zeitpunkt ist es von unschätzbarem Wert, mehrere Verweise auf denselben Wert zuzulassen. Dies ermöglicht Konstrukte wie verknüpfte Listen, Wörterbücher und Diagramme, bei denen mehrere Variablen nicht nur denselben Wert haben müssen, sondern genau dasselbe Objekt im Speicher referenzieren müssen . Die Line-Klasse hat jedoch keine dieser Anforderungen. Der Code des Originalplakats ist also absolut nicht erforderlich
new
.quelle
When you're working with your own classes/objects
... Sie haben oft keinen Grund dazu! Ein winziger Teil der Qs bezieht sich auf Details des Behälterdesigns durch erfahrene Codierer. Im krassen Gegensatz dazu ein deprimierender Anteil ist über Verwirrung der Neulinge , die nicht wissen , die stdlib existiert - oder aktiv schreckliche Zuweisungen in ‚Programmierung‘ ‚Kurse‘ gegeben, wo ein Lehrer fordert sie das Rad neu zu erfinden pointlessly - bevor sie haben sogar lernte, was ein Rad ist und warum es funktioniert. Durch die Förderung einer abstrakteren Zuordnung kann C ++ uns vor Cs endlosem "Segfault mit verknüpfter Liste" bewahren. Bitte, lass es uns .int *i; i = new int[9999];
? Wirklich?int i[9999];
ist viel klarer.)" Ja, es ist klarer, aber um Devil's Advocate zu spielen, ist der Typ nicht unbedingt ein schlechtes Argument. Mit 9999 Elementen kann ich mir ein eng eingebettetes System vorstellen, das nicht genügend Stapel für 9999 Elemente hat: 9999 x 4 Bytes sind ~ 40 kB, x 8 ~ 80 kB. Daher müssen solche Systeme möglicherweise eine dynamische Zuordnung verwenden, vorausgesetzt, sie implementieren sie unter Verwendung eines alternativen Speichers. Dies könnte jedoch möglicherweise nur eine dynamische Zuordnung rechtfertigen, nicht jedochnew
. avector
wäre die eigentliche Lösung in diesem Fallstd::make_unique<int[]>()
natürlich).Zwei Gründe:
delete
dies sonst zu einem Speicherverlust führen kann.quelle
new
ist das neuegoto
.Erinnern Sie sich daran, warum
goto
es so verleumdet ist: Während es ein leistungsfähiges, einfaches Tool zur Flusskontrolle ist, wurde es häufig auf unnötig komplizierte Weise verwendet, was es schwierig machte, Code zu befolgen. Darüber hinaus wurden die nützlichsten und am einfachsten zu lesenden Muster in strukturierten Programmieranweisungen (z. B.for
oderwhile
) codiert . Der ultimative Effekt ist, dass der Code, zu demgoto
der geeignete Weg ist, eher selten ist. Wenn Sie versucht sind zu schreibengoto
, tun Sie die Dinge wahrscheinlich schlecht (es sei denn, Sie wissen wirklich , was Sie tun).new
ist ähnlich - es wird oft verwendet, um Dinge unnötig kompliziert und schwerer lesbar zu machen, und die nützlichsten Verwendungsmuster, die codiert werden können, wurden in verschiedene Klassen codiert. Wenn Sie neue Verwendungsmuster verwenden müssen, für die es noch keine Standardklassen gibt, können Sie außerdem Ihre eigenen Klassen schreiben, die diese codieren!Ich würde sogar behaupten , dass
new
ist schlimmer als aufgoto
Grund der Notwendigkeit, Paarnew
unddelete
Aussagen.Wie
goto
, wenn Sie jemals daran gedacht , müssen Sie verwendennew
, sind Sie wahrscheinlich die Dinge schlecht zu tun - vor allem , wenn Sie so außerhalb der Implementierung einer Klasse , deren Zweck im Leben tun , ist zu kapseln , was dynamische Zuweisungen Sie tun müssen.quelle
Der Hauptgrund ist, dass Objekte auf dem Heap immer schwieriger zu verwenden und zu verwalten sind als einfache Werte. Das Schreiben von Code, der einfach zu lesen und zu warten ist, hat für jeden ernsthaften Programmierer immer oberste Priorität.
Ein weiteres Szenario ist, dass die von uns verwendete Bibliothek eine Wertsemantik bietet und eine dynamische Zuordnung unnötig macht.
Std::string
ist ein gutes Beispiel.Für objektorientierten Code ist es jedoch
new
ein Muss , einen Zeiger zu verwenden , dh ihn zuvor zu erstellen. Um die Komplexität des Ressourcenmanagements zu vereinfachen, verfügen wir über Dutzende von Tools, um es so einfach wie möglich zu gestalten, z. B. intelligente Zeiger. Das objektbasierte Paradigma oder generische Paradigma nimmt eine Wertesemantik an und erfordert weniger oder gar keinnew
, genau wie auf den Postern an anderer Stelle angegeben.Herkömmliche Entwurfsmuster, insbesondere die im GoF- Buch erwähnten , verwenden häufig
new
, da es sich um typischen OO-Code handelt.quelle
For object oriented code, using a pointer [...] is a must
: Unsinn . Wenn Sie 'OO' abwerten, indem Sie sich nur auf eine kleine Teilmenge beziehen, Polymorphismus - auch Unsinn: Referenzen funktionieren auch.[pointer] means use new to create it beforehand
: besonders Unsinn : Referenzen oder Zeiger können auf automatisch zugewiesene Objekte verwendet und polymorph verwendet werden; schau mir .[typical OO code] use new a lot
: vielleicht in einem alten Buch, aber wen interessiert das? Jeder vage moderne C ++ meidetnew
/ rohe Zeiger, wo immer dies möglich ist - und ist dadurch in keiner Weise weniger OOEin weiterer Punkt zu allen oben genannten richtigen Antworten: Es hängt davon ab, welche Art von Programmierung Sie durchführen. Kernel-Entwicklung zum Beispiel in Windows -> Der Stapel ist stark eingeschränkt und Sie können möglicherweise keine Seitenfehler wie im Benutzermodus akzeptieren.
In solchen Umgebungen werden neue oder C-ähnliche API-Aufrufe bevorzugt und sind sogar erforderlich.
Dies ist natürlich nur eine Ausnahme von der Regel.
quelle
new
Ordnet Objekte auf dem Heap zu. Andernfalls werden Objekte auf dem Stapel zugewiesen. Schauen Sie sich den Unterschied zwischen den beiden an .quelle