Frage

Hat jemand hier jemals benutzt C ++ 's ‚Platzierung neu‘? Wenn ja, wofür? Es scheint mir, wie es nur auf Memory-Mapped-Hardware nützlich wäre.

War es hilfreich?

Lösung

Placement neue können Sie ein Objekt im Speicher konstruieren, die bereits vergeben ist.

Sie möchten kann dies für die Optimierung tun, wenn Sie mehrere Instanzen eines Objekts konstruieren müssen, und es ist schneller nicht jedes Mal neu zuteilen Speicher Sie eine neue Instanz benötigen. Stattdessen kann es effizienter sein, eine einzige Zuweisung für einen Teil des Speichers auszuführen, die mehrere Objekte halten können, auch wenn Sie nicht wollen, auf einmal alle, es zu benutzen.

DevX gibt ein gutes Beispiel :

  

Standard C ++ unterstützt auch die Platzierung   neuer Operator, der ein Konstrukt   Objekt auf einem im Voraus zugewiesenen Puffer. Diese   nützlich ist, wenn ein Speicher-Pool bauen,   eine Speicherbereinigungseinrichtung oder einfach, wenn   Leistung und Sicherheit Ausnahme sind   von größter Bedeutung (es gibt keine Gefahr,   Zuordnungsfehler, da der Speicher   bereits zugeteilt worden ist, und   Konstruieren eines Objekts auf einem   vorab zugewiesene Puffer benötigt weniger Zeit):

char *buf  = new char[sizeof(string)]; // pre-allocated buffer
string *p = new (buf) string("hi");    // placement new
string *q = new string("hi");          // ordinary heap allocation

Sie können auch sicher sein wollen, kann es zu einem bestimmten Teil von kritischem Code kein Zuordnungsfehler (zum Beispiel ausgeführt in Code durch einen Schrittmacher). In diesem Fall würden Sie Speicher früher zuzuweisen, und Platzierung verwenden neu im kritischen Abschnitt.

Ausplanung bei der Platzierung neuer

Sie sollten nicht jedes Objekt freigeben, die den Speicherpuffer verwendet. Stattdessen sollten Sie löschen [] nur den ursprünglichen Puffer. Sie müssten dann manuell die Destruktoren der Klassen nennen. Für einen guten Vorschlag zu diesem Thema finden Sie in Stroustrup FAQ an: Gibt es eine „Platzierung löschen“

Andere Tipps

Wir verwenden es mit benutzerdefinierten Speicher-Pools. Nur eine Skizze:

class Pool {
public:
    Pool() { /* implementation details irrelevant */ };
    virtual ~Pool() { /* ditto */ };

    virtual void *allocate(size_t);
    virtual void deallocate(void *);

    static Pool::misc_pool() { return misc_pool_p; /* global MiscPool for general use */ }
};

class ClusterPool : public Pool { /* ... */ };
class FastPool : public Pool { /* ... */ };
class MapPool : public Pool { /* ... */ };
class MiscPool : public Pool { /* ... */ };

// elsewhere...

void *pnew_new(size_t size)
{
   return Pool::misc_pool()->allocate(size);
}

void *pnew_new(size_t size, Pool *pool_p)
{
   if (!pool_p) {
      return Pool::misc_pool()->allocate(size);
   }
   else {
      return pool_p->allocate(size);
   }
}

void pnew_delete(void *p)
{
   Pool *hp = Pool::find_pool(p);
   // note: if p == 0, then Pool::find_pool(p) will return 0.
   if (hp) {
      hp->deallocate(p);
   }
}

// elsewhere...

class Obj {
public:
   // misc ctors, dtors, etc.

   // just a sampling of new/del operators
   void *operator new(size_t s)             { return pnew_new(s); }
   void *operator new(size_t s, Pool *hp)   { return pnew_new(s, hp); }
   void operator delete(void *dp)           { pnew_delete(dp); }
   void operator delete(void *dp, Pool*)    { pnew_delete(dp); }

   void *operator new[](size_t s)           { return pnew_new(s); }
   void *operator new[](size_t s, Pool* hp) { return pnew_new(s, hp); }
   void operator delete[](void *dp)         { pnew_delete(dp); }
   void operator delete[](void *dp, Pool*)  { pnew_delete(dp); }
};

// elsewhere...

ClusterPool *cp = new ClusterPool(arg1, arg2, ...);

Obj *new_obj = new (cp) Obj(arg_a, arg_b, ...);

Jetzt können Sie Objekte gruppieren zusammen in einem einzigen Speicher-Arena, eine allocator auszuwählen, die sehr schnell ist, aber tut keine Freigabe, verwenden Speicher-Mapping und andere semantische wünschen Sie, indem Sie den Pool zu verhängen und weitergeben als Argument ein Plazierungs neue Betreiber des Objekts.

Es ist sinnvoll, wenn Sie Zuordnung von Initialisierung trennen möchten. STL verwendet Platzierung neuer Container-Elemente zu erstellen.

Ich habe es in der Echtzeitprogrammierung verwendet. Wir normalerweise nicht will jede dynamische Zuordnung auszuführen (oder Freigabe), nachdem das System gestartet wird, da es keine Garantie gibt, wie lange das dauern wird.

Was ich tun kann, ist vorbelegen ein großer Teil des Speichers (groß genug, um jede Menge zu halten, was auch immer, dass die Klasse erfordern). Dann, wenn ich zur Laufzeit herausfinden, wie die Dinge zu konstruieren, kann die Platzierung neuer verwendet werden, um Objekte zu konstruieren, genau da, wo ich sie will. Eine Situation, ich weiß, ich benutzte es war in Hilfe eines heterogenen Ringpuffer .

Es ist sicherlich nicht für schwache Nerven, aber das ist, warum sie die Syntax für es irgendwie knorrige machen.

Ich habe es verwendet, um Objekte auf dem Stapel über alloca () zugewiesen zu konstruieren.

schamlose Stecker: ich darüber gebloggt hier .

Kopf Geek: BINGO! Du hast es total - das ist genau das, was es ist perfekt für. In vielen Embedded-Umgebungen, externe Bedingungen und / oder das Gesamtnutzungsszenario zwingt den Programmierer die Zuordnung eines Objekt von seiner Initialisierung zu trennen. In einem Topf geworfen zusammen, ruft C ++ diese „Instantiierung“; aber wenn die Klage des Konstruktor muss explizit ohne dynamische oder automatische Zuordnung aufgerufen werden, die Platzierung neu ist die Möglichkeit, es zu tun. Es ist auch der perfekte Weg, um ein globalen C ++ Objekt zu finden, die auf die Adresse einer Hardware-Komponente (Memory-Mapped I / O), oder für ein statisches Objekt, das aus irgendeinem Grunde fixiert ist, an einer festen Adresse befinden muss.

Ich habe es verwendet, um eine Variant-Klasse zu erstellen (das heißt ein Objekt, das einen einzelnen Wert darstellen kann, der eine aus einer Reihe von verschiedenen Arten sein kann).

Wenn alle Werttypen von der Variant-Klasse unterstützt werden POD-Typen (zB int, float, double, bool) dann eine markierte C-Stil Vereinigung ist ausreichend, aber wenn Sie einige der wert Typen sein wollen C ++ Objekte (zB std :: string), wird die C Vereinigung Funktion nicht tun, als nicht-POD-Datentypen nicht als Teil einer Vereinigung erklärt werden können.

So anstatt zuzuteilen I einen Byte-Array, die groß genug ist (z.B. sizeof (the_largest_data_type_I_support)) und die Verwendung neue Platzierung der geeigneten C ++ Objekt in diesem Bereich zu initialisieren, wenn die Variant-Set ist einen Wert dieser zu halten. (Und Platzierung vorher löschen, wenn sie von einem anderen nicht-POD-Datentyp, natürlich Switchen)

Es ist auch nützlich, wenn Sie global oder statisch zugewiesenen Strukturen neu initialisieren möchten.

Der alte C Weg memset() wurde unter Verwendung aller Elemente auf 0 setzen Sie nicht, dass in C ++ tun können aufgrund vtables und benutzerdefinierte Objektkonstruktoren.

Also ich manchmal verwenden die folgende

 static Mystruct m;

 for(...)  {
     // re-initialize the structure. Note the use of placement new
     // and the extra parenthesis after Mystruct to force initialization.
     new (&m) Mystruct();

     // do-some work that modifies m's content.
 }

Es ist sinnvoll, wenn Sie einen Kernel bauen - wo platziere Sie den Kernel-Code Sie von der Festplatte oder der Seitentabelle lesen? Sie müssen wissen, wo sie zu springen.

oder in andere, sehr seltenen Fällen, wie wenn Sie Lasten der zugewiesenen Zimmer haben und ein paar Strukturen hintereinander platziert werden sollen. Sie können für den offsetof Operator () ohne die Notwendigkeit, diese Art und Weise verpackt werden. Es gibt noch andere Tricks für das auch, wenn.

Ich glaube auch einige STL-Implementierungen Verwendung Platzierung machen neue, wie std :: vector. Sie verteilen Platz für 2 ^ n Elemente, die Art und Weise und müssen nicht immer realloc.

Placement neu ist auch sehr nützlich, wenn (etwa mit boost :: Serialisierung) Serialisierung. In 10 Jahren von c ++ dies ist erst der zweite Fall, den ich Platzierung gebraucht habe neu für (dritte, wenn Sie umfassen Interviews :)).

Ich denke, das ist nicht von irgendeiner Antwort hervorgehoben, aber ein weiteres gutes Beispiel und Verwendung für die neue Platzierung ist die Fragmentierung des Speichers zu reduzieren (durch Speicherpools verwenden). Dies ist besonders nützlich in eingebettet und hochverfügbare Systeme. In diesem letzten Fall ist es besonders wichtig, weil für ein System, das 24/365 Tag ist es sehr wichtig, laufen hat keine Fragmentierung zu haben. Dieses Problem hat nichts mit Speicherlecks zu tun.

Auch wenn eine sehr gute malloc-Implementierung verwendet wird (oder ähnlichen Speichermanagement-Funktion), ist es sehr schwierig, mit Fragmentierung für eine lange Zeit zu beschäftigen. Irgendwann, wenn Sie es nicht schaffen, geschickt die Speicherreservierung / Release ruft Sie mit einer Menge von kleine Lücken könnten am Ende , die nur schwer wieder zu verwenden (neue Buchungen zuweisen). So ist eine der Lösungen, die in diesem Fall verwendet werden, ist ein Speicherpool verwendet werden, bevor die Hand der Speicher für die Anwendungsobjekte zuzuordnen. Nach-Stationen jedes Mal müssen Sie Speicher für ein Objekt verwenden Sie einfach die neue Platzierung ein neues Objekt auf den bereits reservierten Speicher zu erstellen.

Auf diese Weise, wenn Ihre Anwendung gestartet haben Sie bereits alle benötigten Speicher reserviert. Alle neuen Speicherreservierung / Veröffentlichung geht an den zugewiesenen Pools (können Sie mehrere Pools, eine für jede verschiedene Objektklasse). Keine Speicherfragmentierung geschieht in diesem Fall, da es wird keine Lücken und Ihr System kann für sehr lange Zeiträume (Jahre) laufen, ohne Fragmentierung zu leiden.

Ich sah dies in der Praxis speziell für den VxWorks RTOS seit seinem Standardspeicherzuordnungssystem eine Menge von Fragmentierung leidet. So Zuweisung Speicher durch den Standard neu / malloc Verfahren wurde grundsätzlich im Projekt verboten. Alle Speicherreservierungen zu einem dedizierten Speicherpool gehen sollten.

Ich habe es zum Speichern von Objekten mit Memory-Mapped-Dateien verwendet.
Das spezifische Beispiel war eine Bilddatenbank, die Vey eine große Anzahl von großen Bildern verarbeitet (mehr als in den Speicher passen könnte).

Ich habe es als für einen „dynamischen Typ“ Zeiger verwendet gesehen (im Abschnitt "Under the Hood"):

  

Aber hier ist der schwierige Trick, den ich verwendet, um eine hohe Leistung für kleine Typen: wenn der Wert gehalten wird, innerhalb eines void * passen, ich eigentlich nicht die Mühe, ein neues Objekt Zuweisung, zwinge ich sie in den Zeiger selbst Verwendung Platzierung neu.

Es wird von std::vector<> benutzt wird, weil std::vector<> typischerweise mehr Speicher reserviert, als es in den objects vector<> werden.

Es ist eigentlich Art von jeder Art von Datenstruktur zur Implementierung benötigt, die mehr Speicher zuweist, als minimal für die Anzahl der erforderlichen Elemente eingefügt (das heißt, irgendetwas anderes als eine verkettete Struktur, die zu einem Zeitpunkt ein Knoten zuordnet).

Nehmen Sie Container wie unordered_map, vector oder deque. Diese alle mehr Speicher zuzuteilen als minimal die Elemente, die für Sie bisher eingesetzt haben für jede einzelne Einfügung erfordert eine Heapzuordnung zu vermeiden. Lassen Sie uns verwenden vector als einfachstes Beispiel.

Wenn Sie das tun:

vector<Foo> vec;

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

..., der nicht wirklich tausend Foos konstruieren. Es ordnet einfach / Reserven Speicher für sie. Wenn vector nicht neu Platzierung hier verwendet haben, wäre es default-Konstruktion Foos alle über den Ort als auch mit, um ihre Destruktoren aufrufen sogar für Elemente, die Sie noch nie an erster Stelle eingefügt.

Allocation! = Bau, Freeing! = Zerstörung

Gerade im Allgemeinen viele Datenstrukturen zu implementieren, wie oben gesagt, kann man nicht behandeln das Zuweisen von Speicher und Elemente als ein unteilbares Ding konstruieren, und Sie können ebenfalls nicht behandeln Speicherfreigabe und Elemente als eine unteilbare Sache zu zerstören.

Es muss eine Trennung zwischen diesen Ideen sein Überfluß zu vermeiden Konstruktoren und Destruktoren unnötig links und rechts aufrufen, und das ist, warum die Standardbibliothek die Idee std::allocator trennt (die nicht oder konstruieren Elemente zerstören, wenn er / Speicher reserviert befreit *) aus den Behältern entfernt, die sie verwenden, welche Elemente neu und manuell zerstören Elemente mit expliziten Aufrufe von Destruktoren mit Platzierung manuell konstruieren.

  
      
  • Ich hasse das Design von std::allocator aber das ist ein anderes Thema, das ich vermeiden, würde schimpfen. :-D
  •   

Wie auch immer, neige ich dazu, es eine Menge zu verwenden, da ich eine Reihe von Allzweck-standardkonformen C ++ Container geschrieben haben, die nicht in Bezug auf die bestehenden gebaut werden konnte. Eingeschlossen unter ihnen ist eine kleine Vektor Implementierung ich vor ein paar Jahrzehnten gebaut Heapzuweisungen gemeinsam Fällen zu vermeiden, und eine speichereffiziente trie (nicht einem Knoten zu einem Zeitpunkt zugewiesen). In beiden Fällen konnte ich nicht wirklich implementieren sie die vorhandenen Behälter verwenden, und so musste ich placement new verwenden, um auf die Dinge unnötig linken und rechten überflüssigerweise Konstruktoren und Destruktoren zu vermeiden aufgerufen wird.

Natürlich, wenn Sie jemals mit benutzerdefinierten Verteilern arbeiten Objekte einzeln zuweisen, wie eine freie Liste, dann werden Sie auch wollen in der Regel placement new verwenden, wie dieses (einfache Beispiel, die mit Ausnahme-Sicherheit oder RAH nicht stören):

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

Ich habe es verwendet, um Objekte zu erstellen, basierend auf Speicher mit Nachrichten aus dem Netz empfangen werden.

Im Allgemeinen Platzierung neu verwendet Zuteilungs Kosten eines ‚normalen neuen‘ loszuwerden.

Ein weiteres Szenario, in dem ich verwenden es ein Ort ist, wo ich Zugriff auf die Zeiger zu einem Objekt haben wollte, das wurde noch gebaut werden, einen pro-Dokument Singleton zu implementieren.

Der einzige Ort, den ich über sie habe laufe in Containern, die einen zusammenhängenden Puffer zuweisen und sie dann mit Objekten füllen je nach Bedarf. Wie bereits erwähnt, std :: vector dies tun könnte, und ich kenne einige Versionen von MFC CArray und / oder CList tat dies (denn das ist, wo ich über sie zuerst lief). Das Puffer Überzuteilungsverfahren ist eine sehr nützliche Optimierung und Platzierung neu ist so ziemlich die einzige Möglichkeit, Objekte in diesem Szenario zu konstruieren. Es ist auch manchmal zu konstruieren Objekte in den Speicherblöcken zugeordnet außerhalb Ihrer direkten Code verwendet wird.

Ich habe es in einer ähnlichen Kapazität verwendet, obwohl es oft nicht kommen. Es ist ein nützliches Werkzeug für die C ++ Toolbox, though.

Script-Motoren kann es in der nativen Schnittstelle verwenden, um native Objekte aus Skripts zuweisen. Siehe Angelscript (www.angelcode.com/angelscript) für Beispiele.

Sehen Sie die fp.h Datei im xll Projekt unter http://xll.codeplex.com Es löst die „unberechtigt Kumpanei mit dem Compiler“ -Ausgabe für Arrays, die ihre Dimensionen wie mit ihnen tragen.

typedef struct _FP
{
    unsigned short int rows;
    unsigned short int columns;
    double array[1];        /* Actually, array[rows][columns] */
} FP;

Hier ist die Killer-Anwendung für die C ++ in-Place-Konstruktor: zu einer Cache-Zeile ausgerichtet wird, sowie andere Potenzen von 2 Grenzen. Hier ist meine ultra-schnelle Zeiger-Alignment-Algorithmus zu jede Potenz von 2 Grenzen mit 5 oder weniger Single-Cycle-Anweisungen :

/* Quickly aligns the given pointer to a power of two boundary IN BYTES.
@return An aligned pointer of typename T.
@brief Algorithm is a 2's compliment trick that works by masking off
the desired number in 2's compliment and adding them to the
pointer.
@param pointer The pointer to align.
@param boundary_byte_count The boundary byte count that must be an even
power of 2.
@warning Function does not check if the boundary is a power of 2! */
template <typename T = char>
inline T* AlignUp(void* pointer, uintptr_t boundary_byte_count) {
  uintptr_t value = reinterpret_cast<uintptr_t>(pointer);
  value += (((~value) + 1) & (boundary_byte_count - 1));
  return reinterpret_cast<T*>(value);
}

struct Foo { Foo () {} };
char buffer[sizeof (Foo) + 64];
Foo* foo = new (AlignUp<Foo> (buffer, 64)) Foo ();

ist nun nicht nur ein Lächeln auf Ihrem Gesicht (:-). I ♥♥♥ C ++ 1x

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top