Je viens de lire sur std::allocator
. À mon avis, il est plus compliqué de l'utiliser au lieu d'utiliser new
et delete
.
Avec allocator
, nous devons explicitement allouer la mémoire du tas, la construire, la détruire, puis finalement désallouer la mémoire. Alors pourquoi a-t-il été créé?
Dans quels cas peut-il être utilisé et quand doit-il être utilisé au lieu de nouveau et supprimer?
std::allocator
est l'allocateur de mémoire par défaut pour les conteneurs de bibliothèque standard, et vous pouvez remplacer vos propres allocateurs. Cela vous permet de contrôler la façon dont les conteneurs standard allouent la mémoire. Mais je ne pense pas que votre question concerne std::allocator
en particulier, mais plutôt la stratégie d'allocation de mémoire, puis la construction d'objets dans cette mémoire, plutôt que d'utiliser new T[N]
, par exemple.
Et la raison en est que new T[N]
ne vous permet pas de contrôler les noms des constructeurs. Et cela vous oblige à construire tous vos objets en même temps. C'est terrible pour, par exemple, std::vector
où vous ne souhaitez allouer qu'occasionnellement.
Avec un allocateur de mémoire brute, vous pouvez allouer une certaine quantité de mémoire, ce qui détermine votre capacité. Ensuite, lorsque l'utilisateur ajoute des éléments au vecteur (en utilisant le constructeur de son choix), vous pouvez construire des objets en place dans cette mémoire.
Ensuite, lorsque vous manquez de mémoire, vous allouez davantage, généralement deux fois plus. Si std::vector
utilisé new T[N]
, il faudrait réallouer chaque fois que vous vouliez ajouter ou supprimer un élément, ce qui serait terrible pour les performances. Vous seriez également obligé d'utiliser le constructeur par défaut pour tous les objets, ce qui met une restriction inutile sur les types d'objets std::vector
peut tenir.
À mon avis, il est plus compliqué de l'utiliser au lieu d'utiliser new et delete.
Oui, mais il n'est pas destiné à remplacer new
et delete
, il sert un objectif différent.
Avec l'allocateur, nous devons explicitement allouer la mémoire de tas, la construire, la détruire, puis finalement désallouer la mémoire.
Alors pourquoi a-t-il été créé?
Parce que parfois, vous voulez séparer l'allocation et la construction en deux étapes (et de même pour séparer la destruction et la désallocation en deux étapes). Si vous ne voulez pas faire cela, n'utilisez pas d'allocateur, utilisez plutôt new
.
Dans quels cas peut-il être utilisé et quand doit-il être utilisé à la place de nouveau et supprimer?
Lorsque vous avez besoin du comportement d'un allocateur, pas du comportement de new
et delete
, évidemment! Le cas typique est lors de l'implémentation d'un conteneur.
Considérez le code suivant:
std::vector<X> v;
v.reserve(4); // (1)
v.Push_back( X{} ); // (2)
v.Push_back( X{} ); // (3)
v.clear(); // (4)
Ici, la ligne (1) doit allouer suffisamment de mémoire pour quatre objets, mais pas encore les construire. Ensuite, les lignes (2) et (3) doivent construire des objets dans la mémoire allouée. La ligne (4) doit alors détruire ces objets, mais pas désallouer la mémoire. Enfin, dans le destructeur du vecteur, toute la mémoire peut être désallouée.
Ainsi, le vecteur ne peut pas simplement utiliser new X()
ou delete &m_data[1]
Pour créer et détruire les objets, il doit effectuer l'allocation/désallocation séparément de la construction/destruction. L'argument du modèle d'allocateur d'un conteneur définit la politique qui doit être utilisée pour (dé) allouer de la mémoire et construire/détruire des objets, ce qui permet de personnaliser l'utilisation de la mémoire par le conteneur. La stratégie par défaut est le type std::allocator
.
Vous utilisez donc un allocateur lorsqu'un allocateur est requis (comme lors de l'utilisation d'un conteneur) et vous utilisez std::allocator
Lorsque vous ne voulez pas fournir d'allocateur personnalisé et que vous voulez juste celui standard.
Vous n'utilisez pas d'allocateur en remplacement de new
et delete
.
Les allocateurs sont un concept très important dans la STL. Chaque conteneur est capable de prendre un allocateur comme argument. Ensuite, les allocations seront effectuées à l'aide de cet allocateur, et non pas celui standard.
Ceci est utile, par exemple pour allouer des objets de la même taille dans un pool, pour améliorer les performances, ou peut être nécessaire s'il existe une zone de mémoire spéciale où vos objets doivent vivre.
Les étapes d'allocation et de construction sont distinctes car par ex. pour le vecteur ( std::vector::reserve
) il est important de pouvoir allouer de la mémoire pour une utilisation future, mais pas (encore) d'y créer des objets.
En tant que exemple , vous pouvez écrire un allocateur en tant que classe, contenant un tableau de taille fixe, et utiliser ce tableau pour fournir de la mémoire à un conteneur standard. Ensuite, vous pouvez avoir une instance de cette classe sur la pile et ainsi éviter complètement les allocations de tas pour une partie de votre programme.
Voir plus d'exemples ici dans ce SO post.
[...] quand faut-il l'utiliser [...]
Lorsque vous avez des besoins spécifiques et surtout lorsque vous écrivez vos propres conteneurs génériques.
Votre instinct a raison. Dans 90% des cas, utilisez new
. Cependant, notez dans des structures comme, par exemple, la structure de données map . L'un de ses arguments de modèle par défaut est class Alloc = allocator<pair<const Key,T>
, qui définit comment la classe crée de nouvelles instances de choses et gère les instances existantes. De cette façon, vous pourriez théoriquement créer votre propre allocateur, puis l'utiliser pour des structures de données existantes. Puisque new
et delete
sont des fonctions et non des classes, il est nécessaire d'avoir le std::allocator
pour les représenter et leur donner des arguments de modèle valides.
Le std::allocator
a été créé pour permettre aux développeurs de mieux contrôler la façon dont la mémoire est allouée. Dans de nombreux systèmes embarqués, la mémoire est contrainte et de différents types. Il n'y en a peut-être pas énormément. En outre, l'allocation de mémoire doit être minimisée pour éviter les problèmes de fragmentation.
L'allocateur permet également l'allocation à partir de différents pools de mémoire. Ainsi, par exemple, l'allocation de blocs de petite taille serait plus efficace à partir d'un pool de mémoire de petits blocs.
new
et delete
sont le moyen direct de créer un objet dans la mémoire dynamique et de l'initialiser. Les allocateurs sont bien plus que cela, car ils offrent un contrôle complet sur les phases susmentionnées.
Avec l'allocateur, nous devons explicitement allouer la mémoire de tas, la construire, la détruire, puis finalement désallouer la mémoire.
En effet, les allocateurs ne sont pas censés être utilisés pour du code "normal" où new
et delete
conviendrait également. Considérez une classe comme std::map
, souvent implémenté comme un arbre: avez-vous besoin de désallouer la feuille entière chaque fois qu'un objet contenu est supprimé? Les allocateurs vous permettent de détruire cet objet, mais conservez la mémoire de sorte que vous n'ayez plus besoin de l'exiger.
De plus, vous pouvez spécialiser un allocateur pour un certain type si vous connaissez des méthodes plus optimisées pour son contrôle, ce qui n'est pas possible pour new
et delete
.
La raison de cela STL membre est de donner au développeur plus de contrôle sur la mémoire. Ce que je veux dire par là, par exemple, le nouvel opérateur n'est pas vraiment une seule opération en soi. À sa base, il effectue une réservation de mémoire ET remplit ensuite cet espace avec l'objet.
Bien que je ne puisse pas imaginer un scénario de cas concret spécifique, vous devez utiliser std::allocator
et ainsi de suite quand, peut-être, la destruction d'un objet donné pourrait avoir un impact sur d'autres objets en mémoire.
Disons que, pour les besoins de l'argument, vous avez créé une sorte de vecteur auquel chaque élément est lié deux fois à un autre objet en mémoire et vous voulez, au moment de la suppression dudit vecteur, les objets liés pour supprimer la référence à il.