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


409

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.


14
Dies sind nur die Informationen, nach denen ich gesucht habe, um Objektkonstruktoren für Boost-zugewiesene Speicherpools aufzurufen. (In der Hoffnung, dass diese Schlüsselwörter in Zukunft für jemanden leichter zu finden sind).
Sideshow Bob

2
Es wird im Wikipedia-Artikel C ++ 11 im Konstruktor einer Union verwendet.
HelloGoodbye

@ Hallo Auf Wiedersehen, interessant! Warum können Sie in dem Artikel, den Sie verlinkt haben, nicht einfach den p = ptZuweisungsoperator von Pointverwenden und nicht verwenden new(&p) Point(pt)? Ich wundere mich über die Unterschiede zwischen den beiden. Würde der erstere operator=Point aufrufen , während der letztere den Kopierkonstruktor von aufruft Point? aber ich bin mir immer noch nicht ganz klar, warum einer besser ist als der andere.
Andrei-Niculae Petre

@ Andrei-NiculaePetre Ich habe selbst keine neue Platzierung verwendet, aber ich denke, Sie sollten sie - zusammen mit dem Kopierkonstruktor - verwenden, wenn Sie derzeit kein Objekt dieser Klasse haben, andernfalls sollten Sie den Kopierzuweisungsoperator verwenden. Es sei denn, die Klasse ist trivial; dann spielt es keine Rolle, welche von ihnen Sie verwenden. Gleiches gilt für die Zerstörung des Objekts. Wenn dies für nicht triviale Klassen nicht richtig gehandhabt wird, kann dies sehr wahrscheinlich zu seltsamem Verhalten führen und in einigen Situationen sogar zu undefiniertem Verhalten führen .
HelloGoodbye

@ Andrei-NiculaePetre Eigentlich finde ich das Beispiel im Wikipedia-Artikel ziemlich schlecht, da es nur davon ausgeht, dass kein vorheriges Objekt existiert und dass sie eines konstruieren müssen. Dies ist nicht der Fall, wenn U::operator=gerade aufgerufen wurde.
HelloGoodbye

Antworten:


363

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 :

Standard C ++ unterstützt auch die Platzierung eines neuen Operators, der ein Objekt in einem vorab zugewiesenen Puffer erstellt. Dies ist nützlich, wenn Sie einen Speicherpool, einen Garbage Collector erstellen oder einfach, wenn Leistung und Ausnahmesicherheit von größter Bedeutung sind (es besteht keine Gefahr eines Zuordnungsfehlers, da der Speicher bereits zugewiesen wurde und das Erstellen eines Objekts in einem vorab zugewiesenen Puffer weniger Zeit in Anspruch nimmt). ::

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

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" ?


54
Es ist nicht veraltet, da Sie diese Funktion benötigen, um Containerobjekte (wie Vektoren) effizient zu implementieren. Wenn Sie keinen eigenen Container erstellen, müssen Sie diese Funktion jedoch nicht verwenden.
Martin York

26
Es ist auch sehr wichtig, sich daran zu erinnern, #include <memory>
einzuschließen

22
Streng genommen ist es ein undefiniertes Verhalten, delete[]den ursprünglichen charPuffer aufzurufen . Durch die Verwendung der Platzierung newwurde die Lebensdauer der Originalobjekte chardurch die Wiederverwendung ihres Speichers beendet. Wenn Sie jetzt delete[] bufden 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 verwenden operator new/ operator deletezuzuweisen, der für die Verwendung durch Platzierung vorgesehen ist new.
CB Bailey

31
Ich würde definitiv auf die Verwendung des Haufens in einem Schrittmacher verzichten :-)
Eli Bendersky

15
@ RamonZarazua Falscher Header, es ist #include <new>.
Bit2shift

63

Wir verwenden es mit benutzerdefinierten Speicherpools. 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 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.


1
Ja. Wir werden ziemlich schlau, aber es ist kein Thema für diese Frage.
Don Wakefield

2
@jdkoftinoff Haben Sie einen Link zu einem tatsächlichen Codebeispiel? scheint mir ziemlich interessant zu sein!
Victor

@DonWakefield Wie gehen Sie mit der Ausrichtung in diesem Pool um? Sollten Sie die Ausrichtung nicht als Argument an allocate()irgendwo weitergeben?
Mikhail Vasilyev

1
@MikhailVasilyev, in einer echten Implementierung würden Sie das natürlich erledigen. Nur Beispielcode.
Don Wakefield

Was ist, wenn die Platzierung eine ungültige Adresse ist, z. B. 0x0?
Charlie

51

Dies ist nützlich, wenn Sie die Zuordnung von der Initialisierung trennen möchten. STL verwendet die Platzierung neu, um Containerelemente zu erstellen.


35

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.


Hallo TED, können Sie uns bitte mehr über Ihre Lösung mitteilen? Ich denke über eine vorab zugewiesene Lösung nach, habe aber keine großen Fortschritte erzielt. Vielen Dank im Voraus!
Viet

1
Nun, der eigentliche hetrogene zirkuläre Puffercode war wirklich der schwierige Teil, um es richtig zu machen. Das neue Palcement sieht ein wenig grausig aus, aber im Vergleich dazu war es überhaupt kein Problem.
TED

26

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

schamlose Werbung: ich darüber gebloggt hier .


interessanter Artikel, aber ich bin nicht sicher, ob ich den Vorteil verstehe, diesen gegenüber zu verwenden boost::array. Können Sie das etwas näher erläutern?
GrahamS

boost :: array erfordert, dass die Größe des Arrays eine Konstante zur Kompilierungszeit ist. Dies hat diese Einschränkung nicht.
Ferruccio

2
@Ferruccio Das ist ziemlich cool, ich habe bemerkt, dass dein Makro etwas unsicher ist, nämlich Größe könnte ein Ausdruck sein. Wenn zum Beispiel x + 1 übergeben wird, würden Sie es auf sizeof (Typ) * x + 1 erweitern, was falsch wäre. Sie müssen Ihr Makro in Klammern setzen, um es sicherer zu machen.
Benj

Die Verwendung mit alloca erscheint mir gefährlich, wenn eine Ausnahme ausgelöst wird, da Sie die Destruktoren für alle Ihre Objekte aufrufen müssen.
CashCow

14

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.


12

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.)


Ähm, Nicht-POD-Datentypen können innerhalb einer Union deklariert werden, solange Sie einen Union-Ctor angeben - und hey -, dass Ctor wahrscheinlich die Platzierung verwenden würdenew , 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? :)
underscore_d

6
Sie haben alle Versionen von C ++ vor C ++ 11 verpasst, die in vielen Fällen noch unterstützt werden müssen. :)
Jeremy Friesner

10

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 :)).


9

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

 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.
 }

1
Müssten Sie nicht eine entsprechende Zerstörung durchführen, bevor Sie sie auf diese Weise neu initialisieren?
Head Geek

[Zur Rechtschreibung bearbeitet] Normalerweise - das tun Sie. Wenn Sie jedoch wissen, dass die Klasse keinen Speicher oder andere Ressourcen zuweist (oder diese extern freigegeben haben - beispielsweise wenn Sie Speicherpools verwenden), können Sie diese Technik verwenden. Es wird garantiert, dass die V-Tabellenzeiger nicht überschrieben werden. - Nimrodm vor 16 Stunden
Nimrodm

1
Selbst in C wird die Einstellung aller Bits auf 0 nur garantiert, um eine Darstellung von 0 für integrale Typen zu erzeugen, nicht für andere Typen (Nullzeiger können eine Darstellung ungleich Null haben).
Neugieriger

@curiousguy - für primitive Typen sind Sie korrekt (dies macht das Programm vorhersehbar, was beim Debuggen von Vorteil ist). Bei C ++ - Datentypen wird der Konstruktor jedoch ausgeführt (direkt) und ordnungsgemäß initialisiert.
Nimrodm

9

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, vectoroder deque. 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 wir vectorals einfachstes Beispiel.

Wenn Sie das tun:

vector<Foo> vec;

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

... das baut eigentlich nicht tausend Foos. Es reserviert / reserviert einfach Speicher für sie. Wenn vectorhier 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.

  • Ich hasse das Design von, std::allocatoraber das ist ein anderes Thema, über das ich nicht schimpfen werde. :-D

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 newvermeiden, 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 newdiese natürlich auch im Allgemeinen wie folgt verwenden (grundlegendes Beispiel, das sich nicht mit Ausnahmesicherheit oder RAII befasst):

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

8

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.


Das Reduzieren der Speicherzuweisungen ist ein Hauptgrund für die Verwendung sowie "Tricks" wie das Laden von Objekten von der Festplatte
lefticus

Ich kenne keine in C ++ geschriebenen Kernel. Die meisten Kernel sind in Straight C geschrieben.
Adam Rosenfield

8
Das Betriebssystem, mit dem ich die Grundlagen des Betriebssystems gelernt habe, ist in C ++ geschrieben: sweb.sourceforge.net
mstrobl

8

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.


8

Es wird von verwendet, std::vector<>weil std::vector<>normalerweise mehr Speicher zugewiesen wird, als objectsin der vector<>.


7

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).


7

Ich habe gesehen, dass es als leichter Performance-Hack für einen Zeiger vom Typ "Dynamic Type" (im Abschnitt "Under the Hood") verwendet wird:

Aber hier ist der knifflige Trick, den ich verwendet habe, um eine schnelle Leistung für kleine Typen zu erzielen: Wenn der gehaltene Wert in eine Leere * passt, mache ich mir nicht die Mühe, ein neues Objekt zuzuweisen, sondern erzwinge es mit der Platzierung new in den Zeiger .


Was bedeutet es, wenn der gehaltene Wert in eine Leere * passt ? Es ist immer möglich, void * einen beliebigen Zeigertyp zuzuweisen. Können Sie uns bitte ein Beispiel zeigen?
anurag86

@ anurag86: Auf meinem 64-Bit-Computer void*benötigt a 8 Bytes. Es ist ein wenig albern, ein Acht-Byte void*auf ein Ein-Byte zu zeigen bool. Aber es ist durchaus möglich, tatsächlich das Overlay boolauf das void*, ähnlich wie ein union { bool b; void* v }. Sie müssen wissen, dass das, was Sie als a void*bezeichnet haben, tatsächlich a bool(oder a shortoder a floatusw.) ist. Der Artikel, auf den ich verlinkt habe, beschreibt, wie das geht. Um die ursprüngliche Frage zu beantworten, newist die Platzierung die Funktion, mit der ein bool(oder ein anderer Typ) erstellt wird, bei dem ein void*erwartet wird (Casts werden verwendet, um den Wert später abzurufen / zu ändern).
Max Lybbert

@ anurag86: Es ist nicht dasselbe, aber Sie könnten an markierten Zeigern interessiert sein ( en.wikipedia.org/wiki/Tagged_pointer ).
Max Lybbert

6

Ich habe es verwendet, um Objekte basierend auf dem Speicher zu erstellen, der vom Netzwerk empfangene Nachrichten enthält.


5

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.



4

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.


4

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).


3

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.

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

2

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 :

/* 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 ();

Das zaubert dir nicht einfach ein Lächeln ins Gesicht (:-). Ich ♥♥♥ C ++ 1x

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.