Question

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

Était-ce utile?

La solution

L'emplacement nouveau vous permet de construire un objet en mémoire déjà alloué.

Cela peut être utile pour l'optimisation lorsque vous devez créer plusieurs instances d'un objet et qu'il est plus rapide de ne pas réaffecter de la mémoire chaque fois que vous avez besoin d'une nouvelle instance. Au lieu de cela, il serait peut-être plus efficace de procéder à une seule allocation pour un bloc de mémoire pouvant contenir plusieurs objets, même si vous ne souhaitez pas tout utiliser en même temps.

DevX donne un bon exemple :

  

Standard C ++ prend également en charge le placement.   nouvel opérateur, qui construit un   objet sur un tampon pré-alloué. Ce   est utile lors de la construction d'un pool de mémoire,   un éboueur ou simplement quand   la performance et la sécurité des exceptions sont   primordial (il n'y a pas de danger de   échec d'allocation depuis la mémoire   a déjà été alloué, et   construire un objet sur un   le tampon pré-alloué prend moins de temps):

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

Vous pouvez également vous assurer qu'il ne peut y avoir d'échec d'allocation à une certaine partie du code critique (par exemple, dans du code exécuté par un stimulateur cardiaque). Dans ce cas, vous souhaiterez allouer de la mémoire plus tôt, puis utilisez le placement nouveau dans la section critique.

Deallocation in placement new

Vous ne devez pas libérer chaque objet utilisant le tampon de mémoire. Au lieu de cela, vous devez supprimer [] uniquement le tampon d'origine. Vous devez ensuite appeler les destructeurs de vos classes manuellement. Pour une bonne suggestion à ce sujet, veuillez consulter la FAQ de Stroustrup à l'adresse suivante: Existe-t-il une "suppression de placement"? ?

Autres conseils

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

Vous pouvez maintenant regrouper des objets dans une seule arène de mémoire, sélectionner un allocateur très rapide mais ne faisant pas de désallocation, utiliser un mappage de mémoire et toute autre sémantique que vous souhaitez imposer en choisissant le pool et en le passant comme argument. nouvel opérateur du placement d'un objet.

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

Je l'ai utilisé dans la programmation en temps réel. En règle générale, ne ne voulons pas effectuer d'allocation dynamique (ou de désallocation) après le démarrage du système, car rien ne garantit combien de temps cela va prendre.

Ce que je peux faire, c'est préallouer une grande quantité de mémoire (assez grande pour contenir tout le contenu nécessaire à la classe). Ensuite, une fois que j’ai trouvé au moment de l’exécution comment construire les objets, le placement nouveau peut être utilisé pour construire des objets là où je les veux. Je sais que je l’ai utilisée pour créer un tampon circulaire hétérogène.

Ce n’est certainement pas pour les âmes sensibles, mais c’est la raison pour laquelle leur syntaxe est assez grossière.

Je l'ai utilisé pour construire des objets alloués sur la pile via alloca ().

prise sans scrupule: j'ai blogué à ce sujet ici .

Chef Geek: BINGO! Vous avez tout à fait - c'est exactement ce que c'est parfait pour. Dans de nombreux environnements intégrés, des contraintes externes et / ou le scénario d'utilisation globale oblige le programmeur à séparer l'allocation d'un objet de son initialisation. Cumpotés ensemble, C ++ appelle cela "instanciation"; mais chaque fois que l'action du constructeur doit être explicitement appelée SANS allocation dynamique ou automatique, le placement nouveau est le moyen de le faire. C'est également le moyen idéal pour localiser un objet global C ++ épinglé à l'adresse d'un composant matériel (E / S mappée en mémoire) ou pour tout objet 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 objet pouvant représenter une valeur unique pouvant être l'un des types les plus divers).

Si tous les types de valeur pris en charge par la classe Variant sont des types POD (par exemple, int, float, double, bool), une union de style C étiquetée est suffisante, mais si vous souhaitez que certains types de valeur soient Les objets C ++ (par exemple, std :: string) ne feront pas l'affaire, car les types de données autres que POD ne peuvent pas être déclarés dans le cadre d'une union.

Au lieu de cela, j'alloue un tableau d'octets suffisamment grand (par exemple sizeof (the_largest_data_type_I_support)) et utilise placement new pour initialiser l'objet C ++ approprié dans cette zone lorsque le Variant est défini pour contenir une valeur de ce type. (Et bien sûr, supprimez préalablement l'emplacement lorsque vous vous éloignez d'un autre type de données autre que POD)

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

L'ancien moyen C utilisait memset () pour définir tous les éléments sur 0. Vous ne pouvez pas le faire en C ++ en raison des vtables et des constructeurs d'objet personnalisé.

J'utilise donc parfois les éléments suivants

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

Cela est utile si vous construisez un noyau - où placez-vous le code du noyau que vous lisez sur le disque ou la feuille de page? 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 les unes derrière les autres. Ils peuvent être compressés de cette façon sans avoir besoin de l'opérateur offsetof (). Il existe cependant d'autres astuces pour cela.

Je pense aussi que certaines implémentations STL utilisent un placement nouveau, comme std :: vector. Ils allouent ainsi de la place pour 2 ^ n éléments et n'ont pas besoin de toujours réallouer.

Le placement new est également très utile lors de la sérialisation (par exemple avec boost :: serialization). En 10 ans de c ++, il ne s’agit que du deuxième cas pour lequel j’ai besoin d’un nouveau placement (troisième si vous incluez des interviews :)).

Je pense que cela n’a été souligné dans aucune réponse, mais un autre bon exemple et utilisation pour le nouvel emplacement consiste à 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 devant fonctionner 24/365 jours, il est très important de ne pas avoir de fragmentation. Ce problème n'a rien à voir avec une fuite de mémoire.

Même lorsqu'une très bonne implémentation malloc est utilisée (ou une fonction de gestion de mémoire similaire), 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 risquez de vous retrouver avec de nombreux petits espaces difficiles à réutiliser (affectation à de nouvelles réservations). Ainsi, l’une des solutions utilisées dans ce cas consiste à utiliser un pool de mémoire pour allouer à l’avance la mémoire aux objets de l’application. Après chaque fois que vous avez besoin de mémoire pour un objet, vous utilisez simplement le nouvel emplacement pour créer un nouvel objet dans la mémoire déjà réservée.

Ainsi, une fois votre application démarrée, vous avez déjà toute la mémoire nécessaire réservée. 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'objet différente). Aucune fragmentation de la mémoire ne se produit dans ce cas car il n'y aura pas de lacunes et que votre système pourra fonctionner pendant de très longues périodes (années) sans souffrir de fragmentation.

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

Je l'ai utilisé pour stocker des objets avec des fichiers mappés en mémoire.
L’exemple spécifique était une base de données d’images qui traitait un très grand nombre d’images de grande taille (plus que ce qu’il pouvait contenir en mémoire).

Je l'ai vu utilisé comme piratage des performances pour un " type dynamique " pointeur (dans la section "Sous le capot"):

  

Mais voici le truc délicat que j'utilisais pour obtenir des performances rapides pour les petits types: si la valeur détenue peut tenir dans un vide *, je ne me donne pas la peine d'allouer un nouvel objet, je le force dans le pointeur lui-même. en utilisant le placement nouveau.

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

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 requis pour le nombre d'éléments insérés (c'est-à-dire autre chose qu'une structure liée qui alloue un nœud à la fois).

Prenez des conteneurs tels que unordered_map , vector ou deque . Tous ces éléments allouent plus de mémoire que le minimum requis pour les éléments que vous avez insérés jusqu'à présent afin d'éviter de nécessiter une allocation de tas pour chaque insertion. Utilisons vector comme exemple le plus simple.

Quand vous le faites:

vector<Foo> vec;

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

... cela 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 nouveau ici, il s'agirait de construire par défaut Foos et de faire appel à leurs destructeurs même pour des éléments dans lesquels vous n'avez jamais inséré la première place.

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 traiter l'allocation de mémoire et la construction d'éléments comme une seule chose indivisible. Vous ne pouvez pas non plus traiter la libération de mémoire et la destruction d'éléments comme une seule chose indivisible.

Il faut séparer ces idées pour éviter d'appeler inutilement les constructeurs et les destructeurs inutilement gauche et droite. C'est pourquoi la bibliothèque standard sépare l'idée de std :: allocator (ce qui ne construisez ou détruisez des éléments quand il alloue / libère de la mémoire *) des conteneurs qui l'utilisent qui construisent manuellement des éléments en utilisant placement et détruisent manuellement les éléments en utilisant des appels explicites aux destructeurs.

  
      
  • Je déteste la conception de std :: allocator mais c’est un sujet différent pour lequel je ne parlerai pas. :-D
  •   

En tout cas, j’ai tendance à l’utiliser beaucoup depuis que j’ai écrit un certain nombre de conteneurs C ++ universels conformes à la norme qui n’ont pas pu être construits à l’aide des conteneurs existants. Parmi eux, on trouve une petite implémentation de vecteur que j'ai construite il y a quelques décennies pour éviter les allocations de tas dans les cas courants, ainsi qu'un test utilisant une mémoire efficace (ne pas allouer un nœud à la fois). Dans les deux cas, je ne pouvais pas vraiment les implémenter en utilisant les conteneurs existants et je devais donc utiliser placement new pour éviter d'appeler de manière superflue des constructeurs et des destructeurs sur des choses inutiles, à gauche et à droite.

Naturellement, si vous travaillez avec des allocateurs personnalisés pour allouer des objets individuellement, comme une liste libre, vous voudrez aussi généralement utiliser placement new , comme ceci (exemple de base qui ne dérange pas avec exception de sécurité ou RAII):

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

Je l'ai utilisé pour créer des objets basés sur la mémoire contenant des messages reçus du réseau.

En règle générale, le placement nouveau est utilisé pour supprimer le coût d’attribution d’un «nouveau normal».

Un autre scénario dans lequel je l'ai utilisé est un endroit où je souhaitais avoir accès au pointeur sur un objet en cours de construction pour mettre en œuvre un singleton par document.

Cela peut être pratique lors de l’utilisation de 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_cechanitions_construction / a>

Le seul endroit où je suis tombé dessus est dans des conteneurs qui allouent un tampon contigu, puis le remplissent avec des objets selon les besoins. Comme mentionné, std :: vector pourrait faire cela, et je sais que certaines versions de MFC CArray et / ou de CList l'ont fait (parce que c'est là que je l'ai rencontré pour la première fois). La méthode de surallocation de mémoire tampon est une optimisation très utile, et le nouveau placement est pratiquement le seul moyen de construire des objets dans ce scénario. Il est également parfois utilisé pour construire des objets 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 ne soit pas fréquent. C’est cependant un outil utile pour la boîte à outils C ++.

Les moteurs de script peuvent l'utiliser dans l'interface native pour allouer des objets 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 . Elle résout le "conflit intempestif avec le compilateur" question pour les tableaux qui aiment transporter leurs dimensions avec eux.

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

Voici le tueur à utiliser pour le constructeur sur place C ++: alignement sur une ligne de cache, ainsi que d'autres puissances de 2 limites. Voici mon algorithme d'alignement de pointeur ultra-rapide pour toute 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 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 ();

Maintenant, ne vous contentez pas de vous sourire (:-). Je & # 9829; & # 9829; & # 9829; C ++ 1x

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top