Hat hier jemals jemand C ++ 's "Placement New" verwendet? Wenn ja, wofür? Es sieht für mich so aus, als wäre es nur auf speicherabgebildeter Hardware nützlich.
c++
memory-management
new-operator
placement-new
Head Geek
quelle
quelle
p = pt
Zuweisungsoperator vonPoint
verwenden und nicht verwendennew(&p) Point(pt)
? Ich wundere mich über die Unterschiede zwischen den beiden. Würde der erstereoperator=
Point aufrufen , während der letztere den Kopierkonstruktor von aufruftPoint
? aber ich bin mir immer noch nicht ganz klar, warum einer besser ist als der andere.U::operator=
gerade aufgerufen wurde.Antworten:
Mit der neuen Platzierung können Sie ein Objekt im Speicher erstellen, das bereits zugewiesen ist.
Möglicherweise möchten Sie dies zur Optimierung tun, wenn Sie mehrere Instanzen eines Objekts erstellen müssen, und es ist schneller, den Speicher nicht jedes Mal neu zuzuweisen, wenn Sie eine neue Instanz benötigen. Stattdessen ist es möglicherweise effizienter, eine einzelne Zuordnung für einen Speicherblock durchzuführen, der mehrere Objekte enthalten kann, obwohl Sie nicht alle gleichzeitig verwenden möchten.
DevX gibt ein gutes Beispiel :
Möglicherweise möchten Sie auch sicherstellen, dass an einem bestimmten Teil des kritischen Codes (z. B. im von einem Schrittmacher ausgeführten Code) kein Zuordnungsfehler auftritt. In diesem Fall möchten Sie den Speicher früher zuweisen und dann die Platzierung neu innerhalb des kritischen Abschnitts verwenden.
Freigabe bei Platzierung neu
Sie sollten nicht jedes Objekt freigeben, das den Speicherpuffer verwendet. Stattdessen sollten Sie [] nur den ursprünglichen Puffer löschen. Sie müssten dann die Destruktoren Ihrer Klassen manuell aufrufen. Einen guten Vorschlag hierzu finden Sie in den häufig gestellten Fragen zu Stroustrup unter: Gibt es ein "Löschen der Platzierung" ?
quelle
delete[]
den ursprünglichenchar
Puffer aufzurufen . Durch die Verwendung der Platzierungnew
wurde die Lebensdauer der Originalobjektechar
durch die Wiederverwendung ihres Speichers beendet. Wenn Sie jetztdelete[] buf
den dynamischen Typ der Objekte aufrufen, auf die verwiesen wird, stimmt dieser nicht mehr mit ihrem statischen Typ überein, sodass Sie ein undefiniertes Verhalten haben. Es ist konsistenter, Rohspeicher zu verwendenoperator new
/operator delete
zuzuweisen, der für die Verwendung durch Platzierung vorgesehen istnew
.#include <new>
.Wir verwenden es mit benutzerdefinierten Speicherpools. Nur eine Skizze:
Jetzt können Sie Objekte in einer einzigen Speicherarena zusammenfassen, einen Zuordner auswählen, der sehr schnell ist, aber keine Freigabe vornimmt, Speicherzuordnung verwenden und jede andere Semantik verwenden, die Sie auferlegen möchten, indem Sie den Pool auswählen und als Argument an die Platzierung eines Objekts übergeben neuer Betreiber.
quelle
allocate()
irgendwo weitergeben?Dies ist nützlich, wenn Sie die Zuordnung von der Initialisierung trennen möchten. STL verwendet die Platzierung neu, um Containerelemente zu erstellen.
quelle
Ich habe es in der Echtzeitprogrammierung verwendet. Das tun wir normalerweise nicht wollen , jede dynamische Zuordnung (oder Freigabe) durchzuführen , nachdem das System startet, da es keine Garantie gibt , wie lange das dauern wird.
Was ich tun kann, ist, einen großen Teil des Speichers vorab zuzuweisen (groß genug, um eine beliebige Menge von allem aufzunehmen, was die Klasse möglicherweise benötigt). Sobald ich zur Laufzeit herausgefunden habe, wie die Dinge zu konstruieren sind, kann die Platzierung neu verwendet werden, um Objekte genau dort zu konstruieren, wo ich sie haben möchte. Eine Situation, von der ich weiß, dass ich sie verwendet habe, war die Erstellung eines heterogenen Kreispuffers .
Es ist sicherlich nichts für schwache Nerven, aber deshalb machen sie die Syntax dafür irgendwie knorrig.
quelle
Ich habe es verwendet, um Objekte zu erstellen, die über alloca () auf dem Stapel zugewiesen wurden.
schamlose Werbung: ich darüber gebloggt hier .
quelle
boost::array
. Können Sie das etwas näher erläutern?Head Geek: BINGO! Du hast es total verstanden - genau dafür ist es perfekt. In vielen eingebetteten Umgebungen zwingen externe Einschränkungen und / oder das allgemeine Verwendungsszenario den Programmierer, die Zuordnung eines Objekts von seiner Initialisierung zu trennen. Zusammengefasst nennt C ++ diese "Instanziierung"; Wenn jedoch die Aktion des Konstruktors OHNE dynamische oder automatische Zuweisung explizit aufgerufen werden muss, ist die Platzierung neu. Dies ist auch der perfekte Weg, um ein globales C ++ - Objekt zu finden, das an die Adresse einer Hardwarekomponente (speicherabgebildete E / A) oder an ein statisches Objekt gebunden ist, das sich aus irgendeinem Grund an einer festen Adresse befinden muss.
quelle
Ich habe es verwendet, um eine Variantenklasse zu erstellen (dh ein Objekt, das einen einzelnen Wert darstellen kann, der einer von mehreren verschiedenen Typen sein kann).
Wenn alle von der Variant-Klasse unterstützten Werttypen POD-Typen sind (z. B. int, float, double, bool), ist eine getaggte Vereinigung im C-Stil ausreichend. Wenn Sie jedoch möchten, dass einige der Werttypen C ++ - Objekte sind ( zB std :: string), die C-Union-Funktion funktioniert nicht, da Nicht-POD-Datentypen möglicherweise nicht als Teil einer Union deklariert werden.
Stattdessen ordne ich ein Byte-Array zu, das groß genug ist (z. B. sizeof (the_largest_data_type_I_support)), und verwende die Platzierung new, um das entsprechende C ++ - Objekt in diesem Bereich zu initialisieren, wenn die Variante so eingestellt ist, dass sie einen Wert dieses Typs enthält. (Und die Platzierung wird natürlich vorher gelöscht, wenn von einem anderen Nicht-POD-Datentyp gewechselt wird.)
quelle
new
, um seine Nicht-POD-Unterklasse zu initialisieren. Ref: stackoverflow.com/a/33289972/2757035 Die Neuerfindung dieses Rads mit einem beliebig großen Byte-Array ist ein beeindruckendes Stück Akrobatik, scheint aber völlig unnötig. Was habe ich also vermisst? :)Die Platzierung neu ist auch beim Serialisieren sehr nützlich (z. B. mit boost :: serialization). In 10 Jahren C ++ ist dies nur der zweite Fall, für den ich eine neue Platzierung benötigt habe (drittens, wenn Sie Interviews einschließen :)).
quelle
Dies ist auch nützlich, wenn Sie globale oder statisch zugewiesene Strukturen neu initialisieren möchten.
Bei der alten C-Methode wurden
memset()
alle Elemente auf 0 gesetzt. In C ++ ist dies aufgrund von vtables und benutzerdefinierten Objektkonstruktoren nicht möglich.Deshalb benutze ich manchmal Folgendes
quelle
Es ist tatsächlich erforderlich, jede Art von Datenstruktur zu implementieren, die mehr Speicher zuweist, als für die Anzahl der eingefügten Elemente minimal erforderlich ist (dh alles andere als eine verknüpfte Struktur, die jeweils einen Knoten zuweist).
Nehmen Sie Container mögen
unordered_map
,vector
oderdeque
. Diese weisen alle mehr Speicher zu, als für die bisher eingefügten Elemente minimal erforderlich ist, um zu vermeiden, dass für jede einzelne Einfügung eine Heap-Zuordnung erforderlich ist. Verwenden wirvector
als einfachstes Beispiel.Wenn Sie das tun:
... das baut eigentlich nicht tausend Foos. Es reserviert / reserviert einfach Speicher für sie. Wenn
vector
hier keine neue Platzierung verwendet würde, wäre dies eine StandardkonstruktionFoos
überall und die Notwendigkeit, deren Destruktoren auch für Elemente aufzurufen, die Sie noch nie zuvor eingefügt haben.Zuteilung! = Bau, Befreiung! = Zerstörung
Um viele Datenstrukturen wie die oben genannten zu implementieren, können Sie das Zuweisen von Speicher und das Erstellen von Elementen im Allgemeinen nicht als eine unteilbare Sache behandeln, und Sie können das Freigeben von Speicher und das Zerstören von Elementen ebenfalls nicht als eine unteilbare Sache behandeln.
Es muss eine Trennung zwischen diesen Ideen geben, um zu vermeiden, dass Konstruktoren und Destruktoren unnötig links und rechts aufgerufen werden. Deshalb trennt die Standardbibliothek die Idee von
std::allocator
(die keine Elemente konstruiert oder zerstört, wenn sie Speicher zuweist / freigibt *) von Die Container, die es verwenden, erstellen Elemente manuell mithilfe der Platzierung neu und zerstören Elemente manuell mithilfe expliziter Aufrufe von Destruktoren.Daher neige ich dazu, es häufig zu verwenden, da ich eine Reihe von universellen, standardkonformen C ++ - Containern geschrieben habe, die in Bezug auf die vorhandenen nicht erstellt werden konnten. Darunter befindet sich eine kleine Vektorimplementierung, die ich vor einigen Jahrzehnten erstellt habe, um Heap-Zuweisungen in häufigen Fällen zu vermeiden, und ein speichereffizienter Versuch (weist nicht jeweils einen Knoten zu). In beiden Fällen konnte ich sie mit den vorhandenen Containern nicht wirklich implementieren und musste daher
placement new
vermeiden, Konstruktoren und Destruktoren überflüssig auf unnötige Links und Rechts aufzurufen.Wenn Sie jemals mit benutzerdefinierten Zuweisern arbeiten, um Objekte wie eine kostenlose Liste einzeln zuzuweisen, möchten Sie
placement new
diese natürlich auch im Allgemeinen wie folgt verwenden (grundlegendes Beispiel, das sich nicht mit Ausnahmesicherheit oder RAII befasst):quelle
Es ist nützlich, wenn Sie einen Kernel erstellen. Wo platzieren Sie den Kernel-Code, den Sie von der Festplatte oder der Pagetable gelesen haben? Sie müssen wissen, wohin Sie springen müssen.
Oder in anderen, sehr seltenen Fällen, z. B. wenn Sie viel zugewiesenen Raum haben und einige Strukturen hintereinander platzieren möchten. Sie können auf diese Weise gepackt werden, ohne dass der Operator offsetof () erforderlich ist. Es gibt aber auch andere Tricks dafür.
Ich glaube auch, dass einige STL-Implementierungen die Platzierung neu nutzen, wie z. B. std :: vector. Sie weisen auf diese Weise Platz für 2 ^ n Elemente zu und müssen nicht immer neu zugewiesen werden.
quelle
Ich denke, dies wurde durch keine Antwort hervorgehoben, aber ein weiteres gutes Beispiel und eine gute Verwendung für die neue Platzierung ist die Reduzierung der Speicherfragmentierung (durch Verwendung von Speicherpools). Dies ist besonders nützlich in eingebetteten Systemen und Systemen mit hoher Verfügbarkeit. In diesem letzten Fall ist dies besonders wichtig, da es für ein System, das 24/365 Tage laufen muss, sehr wichtig ist, keine Fragmentierung zu haben. Dieses Problem hat nichts mit Speicherverlust zu tun.
Selbst wenn eine sehr gute Malloc-Implementierung (oder eine ähnliche Speicherverwaltungsfunktion) verwendet wird, ist es sehr schwierig, sich lange Zeit mit Fragmentierung zu befassen. Wenn Sie die Speicherreservierungs- / Freigabeaufrufe nicht geschickt verwalten, kann es zu vielen kleinen Lücken kommen , die schwer wiederzuverwenden sind (neuen Reservierungen zuweisen). Eine der in diesem Fall verwendeten Lösungen besteht darin, einen Speicherpool zu verwenden, um den Speicher für die Anwendungsobjekte vorab zuzuweisen. Nachher jedes Mal, wenn Sie Speicher für ein Objekt benötigen, verwenden Sie einfach die neue Platzierung , um ein neues Objekt im bereits reservierten Speicher zu erstellen.
Auf diese Weise haben Sie nach dem Start Ihrer Anwendung bereits den gesamten benötigten Speicher reserviert. Die gesamte neue Speicherreservierung / -freigabe geht an die zugewiesenen Pools (Sie können mehrere Pools haben, einen für jede unterschiedliche Objektklasse). In diesem Fall tritt keine Speicherfragmentierung auf, da keine Lücken entstehen und Ihr System sehr lange Zeiträume (Jahre) laufen kann, ohne unter Fragmentierung zu leiden.
Ich habe dies in der Praxis speziell für das VxWorks RTOS gesehen, da sein Standard-Speicherzuweisungssystem stark unter Fragmentierung leidet. Das Zuweisen von Speicher über die Standardmethode new / malloc war im Projekt grundsätzlich verboten. Alle Speicherreservierungen sollten in einen dedizierten Speicherpool verschoben werden.
quelle
Es wird von verwendet,
std::vector<>
weilstd::vector<>
normalerweise mehr Speicher zugewiesen wird, alsobjects
in dervector<>
.quelle
Ich habe es zum Speichern von Objekten mit Speicherzuordnungsdateien verwendet.
Das spezifische Beispiel war eine Bilddatenbank, die sehr viele große Bilder verarbeitete (mehr als in den Speicher passen konnte).
quelle
Ich habe gesehen, dass es als leichter Performance-Hack für einen Zeiger vom Typ "Dynamic Type" (im Abschnitt "Under the Hood") verwendet wird:
quelle
void*
benötigt a 8 Bytes. Es ist ein wenig albern, ein Acht-Bytevoid*
auf ein Ein-Byte zu zeigenbool
. Aber es ist durchaus möglich, tatsächlich das Overlaybool
auf dasvoid*
, ähnlich wie einunion { bool b; void* v }
. Sie müssen wissen, dass das, was Sie als avoid*
bezeichnet haben, tatsächlich abool
(oder ashort
oder afloat
usw.) ist. Der Artikel, auf den ich verlinkt habe, beschreibt, wie das geht. Um die ursprüngliche Frage zu beantworten,new
ist die Platzierung die Funktion, mit der einbool
(oder ein anderer Typ) erstellt wird, bei dem einvoid*
erwartet wird (Casts werden verwendet, um den Wert später abzurufen / zu ändern).Ich habe es verwendet, um Objekte basierend auf dem Speicher zu erstellen, der vom Netzwerk empfangene Nachrichten enthält.
quelle
Im Allgemeinen wird die Platzierung neu verwendet, um die Zuordnungskosten eines "normalen neuen" zu beseitigen.
Ein anderes Szenario, in dem ich es verwendet habe, ist ein Ort, an dem ich Zugriff auf den Zeiger auf ein Objekt haben wollte, das noch erstellt werden musste, um einen Singleton pro Dokument zu implementieren.
quelle
Dies kann unter anderem bei der Verwendung von Shared Memory hilfreich sein ... Beispiel: http://www.boost.org/doc/libs/1_51_0/doc/html/interprocess/synchronization_mechanisms.html#interprocess.synchronization_mechanisms.conditions. Bedingungen_anonym_beispiel
quelle
Die einzige Stelle, auf die ich gestoßen bin, sind Container, die einen zusammenhängenden Puffer zuweisen und ihn dann nach Bedarf mit Objekten füllen. Wie bereits erwähnt, könnte std :: vector dies tun, und ich weiß, dass einige Versionen von MFC CArray und / oder CList dies getan haben (weil ich dort zum ersten Mal darauf gestoßen bin). Die Pufferüberzuweisungsmethode ist eine sehr nützliche Optimierung, und die Platzierung neu ist so ziemlich die einzige Möglichkeit, Objekte in diesem Szenario zu erstellen. Es wird manchmal auch verwendet, um Objekte in Speicherblöcken zu erstellen, die außerhalb Ihres direkten Codes zugewiesen sind.
Ich habe es in ähnlicher Funktion verwendet, obwohl es nicht oft vorkommt. Es ist jedoch ein nützliches Tool für die C ++ - Toolbox.
quelle
Skript-Engines können es in der nativen Oberfläche verwenden, um native Objekte aus Skripten zuzuweisen. Beispiele finden Sie unter Angelscript (www.angelcode.com/angelscript).
quelle
Weitere Informationen finden Sie in der Datei fp.h im xll-Projekt unter http://xll.codeplex.com. Sie löst das Problem "ungerechtfertigte Probleme mit dem Compiler" für Arrays, die ihre Dimensionen gerne mit sich herumtragen.
quelle
Hier ist die Killer-Verwendung für den C ++ - In-Place-Konstruktor: Ausrichten an einer Cache-Zeile sowie andere Potenzen mit zwei Grenzen. Hier ist mein ultraschneller Zeigerausrichtungsalgorithmus für jede Potenz von 2 Grenzen mit 5 oder weniger Einzelzyklusanweisungen :
Das zaubert dir nicht einfach ein Lächeln ins Gesicht (:-). Ich ♥♥♥ C ++ 1x
quelle