515 Stimmen

Welche Verwendungszwecke gibt es für "Platzierung neu"?

Hat irgendjemand hier jemals die "Platzierung neu" von C++ verwendet? Wenn ja, wofür? Für mich sieht es so aus, als ob es nur auf memory-mapped Hardware nützlich wäre.

19voto

Es ist eigentlich irgendwie erforderlich, um jede Art von Datenstruktur zu implementieren, die mehr Speicher als minimal für die Anzahl der eingefügten Elemente benötigt (d.h. alles andere als eine verknüpfte Struktur, die einen Knoten zu einem Zeitpunkt zuweist).

Nehmen Sie Behälter wie unordered_map , vector , oder deque . Sie alle weisen mehr Speicher zu, als für die bisher eingefügten Elemente minimal erforderlich ist, damit nicht für jede einzelne Einfügung eine Heap-Zuweisung erforderlich ist. Verwenden wir vector als das einfachste Beispiel.

Wenn Sie das tun:

vector<Foo> vec;

// Allocate memory for a thousand Foos:
vec.reserve(1000);

... die nicht wirklich tausend Foos konstruiert. Es wird lediglich Speicher für sie reserviert/zugewiesen. Wenn vector hier keine neue Platzierung verwenden würde, wäre es eine Standard-Konstruktion Foos überall und müssen ihre Destruktoren auch für Elemente aufrufen, die Sie gar nicht erst eingefügt haben.

Zuteilung != Aufbau, Freisetzung != Zerstörung

Um viele Datenstrukturen wie die oben genannten zu implementieren, können Sie die Zuweisung von Speicher und die Konstruktion von Elementen nicht als eine unteilbare Sache behandeln, und Sie können auch das Freigeben von Speicher und das Zerstören von Elementen nicht als eine unteilbare Sache behandeln.

Es muss eine Trennung zwischen diesen Ideen geben, um zu vermeiden, dass Konstruktoren und Destruktoren unnötigerweise links und rechts aufgerufen werden, und deshalb trennt die Standardbibliothek die Idee von std::allocator (die keine Elemente konstruiert oder zerstört, wenn sie Speicher zuweist/freigebt*), weg von den Containern, die sie verwenden, die manuell Elemente konstruieren, indem sie new platzieren und manuell Elemente zerstören, indem sie explizite Aufrufe von Destruktoren verwenden.

  • Ich hasse das Design von std::allocator aber das ist ein anderes Thema, über das ich mich nicht auslassen will :-D

Wie auch immer, ich verwende sie häufig, da ich eine Reihe von standardkonformen C++-Containern für allgemeine Zwecke geschrieben habe, die mit den vorhandenen Containern nicht gebaut werden konnten. Dazu gehört eine kleine Vektor-Implementierung, die ich vor ein paar Jahrzehnten gebaut habe, um Heap-Allokationen in häufigen Fällen zu vermeiden, und ein speichereffizientes Trie (das nicht einen Knoten auf einmal alloziert). In beiden Fällen konnte ich sie nicht wirklich mit den vorhandenen Containern implementieren und musste daher die placement new um zu vermeiden, dass überflüssigerweise Konstruktoren und Destruktoren für unnötige Dinge links und rechts aufgerufen werden.

Wenn Sie jemals mit benutzerdefinierten Zuweisern arbeiten, um Objekte individuell zuzuweisen, wie z.B. eine freie Liste, dann werden Sie im Allgemeinen auch die placement new (einfaches Beispiel, das sich nicht um die Ausnahmesicherheit oder RAII kümmert):

Foo* foo = new(free_list.allocate()) Foo(...);
...
foo->~Foo();
free_list.free(foo);

14voto

Jeremy Friesner Punkte 63019

Ich habe damit eine Variant-Klasse erstellt (d. h. ein Objekt, das einen einzelnen Wert darstellen kann, der einer von mehreren Typen sein kann).

Wenn alle von der Variant-Klasse unterstützten Wertetypen POD-Typen sind (z.B. int, float, double, bool), dann ist eine Union im C-Stil ausreichend, aber wenn einige der Wertetypen C++-Objekte sein sollen (z.B. std::string), reicht die C-Union-Funktion nicht aus, da Nicht-POD-Datentypen nicht als Teil einer Union deklariert werden dürfen.

Also weise ich stattdessen ein Byte-Array zu, das groß genug ist (z.B. sizeof(the_largest_data_type_I_support)) und benutze placement new, um das entsprechende C++-Objekt in diesem Bereich zu initialisieren, wenn die Variant so eingestellt ist, dass sie einen Wert dieses Typs enthält. (Und ich rufe natürlich vorher manuell den Destruktor des Objekts auf, wenn ich zu einem anderen Datentyp wechsle)

14voto

dorcsssc Punkte 141

Chef-Geek: BINGO! Du hast es erfasst - das ist genau das, wofür es perfekt ist. In vielen eingebetteten Umgebungen zwingen externe Beschränkungen und/oder das allgemeine Nutzungsszenario den Programmierer dazu, die Zuweisung eines Objekts von seiner Initialisierung zu trennen. Zusammengefasst nennt C++ dies "Instanziierung"; aber immer wenn die Aktion des Konstruktors explizit OHNE dynamische oder automatische Zuweisung aufgerufen werden muss, ist "Placement New" der richtige Weg. Es ist auch der perfekte Weg, um ein globales C++-Objekt zu lokalisieren, das an die Adresse einer Hardwarekomponente gebunden ist (memory-mapped I/O), oder für jedes statische Objekt, das, aus welchem Grund auch immer, an einer festen Adresse residieren muss.

10voto

RichardBruce Punkte 611

Placement new ist auch sehr nützlich bei der Serialisierung (z.B. mit boost::serialization). In 10 Jahren C++ ist dies erst der zweite Fall, in dem ich Placement New benötige (der dritte, wenn man Interviews mitzählt :)).

10voto

rkachach Punkte 15236

Ich denke, dies wurde in keiner Antwort hervorgehoben, aber ein weiteres gutes Beispiel und eine weitere Verwendung für die neue Platzierung ist es, die Speicherfragmentierung zu verringern (durch Verwendung von Speicherpools). Dies ist besonders in eingebetteten und hochverfügbaren Systemen nützlich. In letzterem Fall ist es besonders wichtig, weil es für ein System, das 24/365 Tage lang laufen muss, sehr wichtig ist, keine Fragmentierung zu haben. Dieses Problem hat nichts mit Speicherverlusten zu tun.

Selbst wenn eine sehr gute malloc-Implementierung verwendet wird (oder eine ähnliche Speicherverwaltungsfunktion), ist es sehr schwierig, die Fragmentierung für eine lange Zeit zu bewältigen. Irgendwann, wenn man die Aufrufe zur Speicherreservierung und -freigabe nicht geschickt verwaltet, könnte man mit einer Menge von kleine Lücken die nur schwer wiederverwendet werden können (Zuordnung zu neuen Reservierungen). Eine der Lösungen, die in diesem Fall verwendet werden, ist die Verwendung eines Speicherpools, um den Speicher für die Anwendungsobjekte im Voraus zuzuweisen. Jedes Mal, wenn Sie anschließend Speicher für ein Objekt benötigen, verwenden Sie einfach die neue Platzierung um ein neues Objekt auf dem bereits reservierten Speicher zu erstellen.

Auf diese Weise haben Sie, sobald Ihre Anwendung startet, bereits den gesamten benötigten Speicher reserviert. Alle neuen Speicherreservierungen bzw. -freigaben gehen an die zugewiesenen Pools (Sie können mehrere Pools haben, einen für jede verschiedene Objektklasse). In diesem Fall kommt es zu keiner Speicherfragmentierung, da es keine Lücken gibt und Ihr System über sehr lange Zeiträume (Jahre) laufen kann, ohne dass es zu einer Fragmentierung kommt.

Ich habe dies in der Praxis speziell für das VxWorks RTOS gesehen, da dessen Standard-Speicherzuordnungssystem sehr unter Fragmentierung leidet. Daher war die Zuweisung von Speicher über die Standardmethode new/malloc in diesem Projekt grundsätzlich verboten. Alle Speicherreservierungen sollten in einen speziellen Speicherpool fließen.

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