Quels sont les usages du «placement nouveau»?

Quelqu’un at-il déjà utilisé le “placement new” de C ++? Si oui, pour quoi faire? Il me semble que cela ne serait utile que sur du matériel mappé en mémoire.

    Placement new vous permet de construire un object sur la mémoire qui est déjà alloué.

    Vous pouvez le faire pour les optimisations (il est plus rapide de ne pas ré-allouer tout le temps), mais vous devez reconstruire un object plusieurs fois. Si vous devez continuer à réaffecter, il peut être plus efficace d’allouer plus que vous n’en avez besoin, même si vous ne voulez pas encore l’utiliser.

    Devex donne un bon exemple :

    Le standard C ++ prend également en charge l’opérateur de placement new, qui construit un object sur un tampon pré-alloué. Ceci est utile lorsque vous construisez un pool de mémoire, un ramasse-miettes ou simplement lorsque la sécurité des performances et des exceptions est primordiale (il n’y a aucun risque d’échec d’allocation car la mémoire a déjà été allouée et la construction :

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

    Vous voudrez peut-être aussi vous assurer qu’il ne peut y avoir d’échec d’allocation à un certain niveau de code critique (vous travaillez peut-être sur un stimulateur cardiaque par exemple). Dans ce cas, vous souhaitez utiliser le placement nouveau.

    Affectation en placement nouveau

    Vous ne devez pas désallouer tous les objects qui utilisent la mémoire tampon. Au lieu de cela, vous devez supprimer [] uniquement le tampon d’origine. Vous devrez alors appeler les destructeurs directement de vos classes manuellement. Pour une bonne suggestion à ce sujet, veuillez consulter la FAQ de Stroustrup sur: Existe – t-il une “suppression de placement” ?

    Nous l’utilisons avec des pools de mémoire personnalisés. Juste un croquis:

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

    Maintenant, vous pouvez regrouper des objects dans un même espace mémoire, sélectionner un allocateur très rapide mais sans désallocation, utiliser le mappage de mémoire et toute autre sémantique que vous souhaitez imposer en choisissant le pool et en le transmettant comme argument au placement d’un object. nouvel opérateur.

    C’est utile si vous souhaitez séparer l’allocation de l’initialisation. STL utilise le placement new pour créer des éléments de conteneur.

    Je l’ai utilisé dans la programmation en temps réel. Nous ne souhaitons généralement pas effectuer d’allocation dynamic (ou de désallocation) après le démarrage du système, car rien ne garantit la durée de la procédure.

    Ce que je peux faire est de pré-allouer une grande partie de la mémoire (assez grande pour contenir tout ce que la classe peut exiger). Ensuite, une fois que j’ai compris à l’exécution comment construire les choses, le nouveau placement peut être utilisé pour construire des objects là où je les veux. Une situation que je sais utiliser pour aider à créer un tampon circulaire hétérogène.

    Ce n’est certainement pas pour les faibles de cœur, mais c’est la raison pour laquelle ils en font la syntaxe un peu trop grossière.

    Je l’ai utilisé pour construire des objects alloués sur la stack via alloca ().

    fiche sans vergogne: j’ai blogué à ce sujet ici .

    Chef Geek: BINGO! Vous l’avez tout à fait – c’est exactement ce que c’est parfait. Dans de nombreux environnements embarqués, des contraintes externes et / ou le scénario d’utilisation global obligent le programmeur à séparer l’allocation d’un object de son initialisation. Ensemble, C ++ appelle cette “instanciation”; mais chaque fois que l’action du constructeur doit être explicitement appelée SANS affectation dynamic ou automatique, le placement new est le moyen de le faire. C’est aussi le moyen idéal pour localiser un object C ++ global épinglé à l’adresse d’un composant matériel (mémoire mappée), ou pour tout object statique qui, pour une raison quelconque, doit résider à une adresse fixe.

    Je l’ai utilisé pour créer une classe Variant (c’est-à-dire un object pouvant représenter une valeur unique pouvant être de plusieurs types différents).

    Si tous les types de valeurs supportés par la classe Variant sont des types POD (par exemple, int, float, double, bool), une union de style C balisée suffit, mais si vous souhaitez que certains types de valeur soient des objects C ++ ( par exemple std :: ssortingng), la fonction d’union C ne fonctionne pas, car les types de données non-POD peuvent ne pas être déclarés dans le cadre d’une union.

    Donc, à la place, j’alloue un tableau d’octets suffisamment grand (par exemple sizeof (the_largest_data_type_I_support)) et utilise le placement new pour initialiser l’object C ++ approprié dans cette zone lorsque le Variant est défini pour contenir une valeur de ce type. (Et bien entendu, supprimer l’emplacement lors de l’abandon d’un autre type de données non-POD)

    C’est utile si vous construisez un kernel – où placez-vous le code du kernel que vous lisez sur le disque ou la pagetable? Vous devez savoir où aller.

    Ou dans d’autres circonstances très rares, par exemple lorsque vous avez beaucoup de place et que vous souhaitez placer quelques structures l’une derrière l’autre. Ils peuvent être compressés de cette façon sans avoir besoin de l’opérateur offsetof (). Il y a d’autres astuces pour ça aussi.

    Je pense également que certaines implémentations STL utilisent le placement new, comme std :: vector. Ils allouent de la place pour 2 ^ n éléments de cette façon et n’ont pas besoin de toujours se ré-affecter.

    C’est également utile lorsque vous souhaitez réinitialiser des structures globales ou statiquement allouées.

    L’ancienne méthode C utilisait memset() pour définir tous les éléments sur 0. Vous ne pouvez pas faire cela en C ++ en raison de vtables et de constructeurs d’objects personnalisés.

    Donc, j’utilise parfois ce qui suit

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

    Placement new est également très utile lors de la sérialisation (par exemple avec boost :: serialization). En 10 ans de c ++, ce n’est que le deuxième cas dont j’ai besoin pour un nouveau placement (troisième si vous incluez des interviews :)).

    Je pense que cela n’a pas été mis en évidence par une réponse, mais un autre bon exemple et utilisation pour le nouvel emplacement est de réduire la fragmentation de la mémoire (en utilisant des pools de mémoire). Ceci est particulièrement utile dans les systèmes embarqués et à haute disponibilité. Dans ce dernier cas, c’est particulièrement important car pour un système qui doit fonctionner 24/365 jours, il est très important de ne pas avoir de fragmentation. Ce problème n’a rien à voir avec les memory leaks.

    Même lorsqu’une très bonne implémentation malloc est utilisée (ou une fonction similaire de gestion de la mémoire), il est très difficile de gérer la fragmentation pendant longtemps. À un moment donné, si vous ne gérez pas intelligemment les appels de réservation / libération de mémoire, vous pourriez vous retrouver avec beaucoup de petites lacunes difficiles à réutiliser (atsortingbuer à de nouvelles réservations). Ainsi, l’une des solutions utilisées dans ce cas consiste à utiliser un pool de mémoire pour allouer au préalable la mémoire des objects d’application. Après chaque fois que vous avez besoin de mémoire pour un object, utilisez simplement le nouvel emplacement pour créer un nouvel object sur la mémoire déjà réservée.

    De cette façon, une fois que votre application démarre, vous disposez déjà de toute la mémoire nécessaire. Toute la nouvelle réservation / libération de mémoire va aux pools alloués (vous pouvez avoir plusieurs pools, un pour chaque classe d’object différente). Aucune fragmentation de la mémoire ne se produit dans ce cas car il n’y aura pas de lacunes et votre système pourra fonctionner pendant de très longues périodes (années) sans subir de fragmentation.

    Je l’ai vu en pratique spécialement pour le RTOS de VxWorks, car son système d’allocation de mémoire par défaut souffre beaucoup de la fragmentation. Ainsi, l’allocation de mémoire via la nouvelle méthode standard / malloc était fondamentalement interdite dans le projet. Toutes les réservations de mémoire doivent aller à un pool de mémoire dédié.

    Il est utilisé par std::vector<> car std::vector<> alloue généralement plus de mémoire qu’il n’y a d’ objects dans le vector<> .

    Je l’ai utilisé pour stocker des objects avec des fichiers mappés en mémoire.
    L’exemple spécifique était une firebase database d’images qui traitait un grand nombre d’images volumineuses (plus que ce qui pouvait entrer en mémoire).

    Je l’ai utilisé pour créer des objects en fonction de la mémoire contenant les messages reçus du réseau.

    Je l’ai vu utilisé comme un léger hack de performance pour un pointeur “type dynamic” (dans la section “Under the Hood”):

    Mais voici le truc délicat que j’utilisais pour obtenir des performances rapides pour les petits types: si la valeur retenue peut tenir à l’intérieur d’un vide *, je n’atsortingbue pas vraiment un nouvel object, je le force dans le pointeur .

    En règle générale, le placement nouveau est utilisé pour se débarrasser des coûts de répartition d’une «nouvelle normale».

    Un autre scénario où je l’ai utilisé est un endroit où je voulais avoir access au pointeur vers un object qui devait encore être construit, pour implémenter un singleton par document.

    Cela peut être utile lorsque vous utilisez la mémoire partagée, entre autres utilisations … Par exemple: http://www.boost.org/doc/libs/1_51_0/doc/html/interprocess/synchronization_mechanisms.html#interprocess.synchronization_mechanisms.conditions. conditions_anonymous_example

    Il est en fait nécessaire d’implémenter tout type de structure de données qui alloue plus de mémoire que le minimum requirejs pour le nombre d’éléments insérés (c.-à-d. Autre chose qu’une structure liée qui alloue un nœud à la fois).

    Prenez des conteneurs comme unordered_map , vector ou deque . Celles-ci allouent plus de mémoire que ce qui est minimalement requirejs pour les éléments que vous avez insérés jusqu’à présent, afin d’éviter de nécessiter une allocation de segment de mémoire pour chaque insertion. Utilisons le vector comme exemple le plus simple.

    Quand tu fais:

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

    … qui ne construit pas réellement un millier de Foos. Il leur alloue / réserve simplement de la mémoire. Si vector n’utilisait pas le placement new ici, ce serait la construction par défaut de Foos tous les Foos ainsi que le fait d’invoquer leurs destructeurs même pour des éléments que vous n’avez même jamais insérés.

    Allocation! = Construction, Libération! = Destruction

    De manière générale, pour implémenter de nombreuses structures de données comme celle ci-dessus, vous ne pouvez pas considérer l’allocation de mémoire et la construction d’éléments comme une chose indivisible, et vous ne pouvez pas non plus considérer la libération de mémoire et la destruction d’éléments comme une chose indivisible.

    Il faut séparer ces idées pour éviter d’invoquer inutilement des constructeurs et des destructeurs à gauche et à droite, et c’est pourquoi la bibliothèque standard sépare l’idée de std::allocator (qui ne construit ni ne détruit des éléments lorsqu’il alloue / libère de la mémoire) *) loin des conteneurs qui l’utilisent et qui construisent manuellement des éléments en utilisant le placement new et détruisent manuellement des éléments en utilisant des invocations explicites de destructeurs.

    • Je déteste la conception de std::allocator mais c’est un sujet différent sur lequel je ne vais pas me plaindre. :-RÉ

    Donc, de toute façon, j’ai tendance à l’utiliser beaucoup depuis que j’ai écrit un certain nombre de conteneurs C ++ standard à usage général qui ne pouvaient pas être construits avec les conteneurs existants. Parmi celles-ci, il y a une petite implémentation de vecteurs que j’ai construite il y a quelques décennies pour éviter les allocations de tas dans les cas courants et un sortinge efficace en mémoire (n’alloue pas un nœud à la fois). Dans les deux cas, je ne pouvais pas vraiment les implémenter en utilisant les conteneurs existants, et j’ai donc dû utiliser le placement new pour éviter d’invoquer des constructeurs et des destructeurs de manière superflue sur des choses inutiles à gauche et à droite.

    Naturellement, si vous travaillez avec des allocateurs personnalisés pour atsortingbuer des objects individuellement, comme une liste libre, vous souhaiterez également utiliser le placement new comme ceci (exemple de base qui ne dérange pas avec la sécurité des exceptions ou RAII):

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

    Le seul endroit où je l’ai parcouru est dans des conteneurs qui allouent un tampon contigu, puis le remplissent d’objects, le cas échéant. Comme mentionné, std :: vector pourrait le faire, et je sais que certaines versions de MFC CArray et / ou de CList l’ont fait (car c’est là que je l’ai parcouru pour la première fois). La méthode d’allocation de la mémoire tampon est une optimisation très utile, et la nouvelle méthode de placement est pratiquement la seule façon de créer des objects dans ce scénario. Parfois, il est également utilisé pour construire des objects dans des blocs de mémoire alloués en dehors de votre code direct.

    Je l’ai utilisé dans une capacité similaire, bien que cela n’arrive pas souvent. C’est un outil utile pour la boîte à outils C ++.

    Les moteurs de script peuvent l’utiliser dans l’interface native pour allouer des objects natifs à partir de scripts. Voir Angelscript (www.angelcode.com/angelscript) pour des exemples.

    Consultez le fichier fp.h dans le projet xll à l’adresse http://xll.codeplex.com. Il résout le problème du “bourrage injustifié avec le compilateur” pour les baies qui aiment transporter leurs dimensions avec elles.

     typedef struct _FP
     {
         des lignes int non signées courtes;
         colonnes int non signées;
         double tableau [1];  / * En fait, array [rows] [columns] * /
     } FP;
    

    Voici l’utilisation killer du constructeur in situ C ++: l’alignement sur une ligne de cache, ainsi que d’autres puissances de 2 limites. Voici mon algorithme d’alignement de pointeur ultra-rapide sur n’importe quelle puissance de 2 limites avec 5 instructions à cycle unique ou moins :

     /* 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 sortingck 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  inline T* AlignUp(void* pointer, uintptr_t boundary_byte_count) { uintptr_t value = reinterpret_cast(pointer); value += (((~value) + 1) & (boundary_byte_count - 1)); return reinterpret_cast(value); } struct Foo { Foo () {} }; char buffer[sizeof (Foo) + 64]; Foo* foo = new (AlignUp (buffer, 64)) Foo (); 

    Maintenant, cela ne vous fait pas sourire (:-). Je ♥♥♥ C ++ 1x