polymorphic_allocator: quand et pourquoi devrais-je l'utiliser?

122

Voici la documentation sur cppreference , ici le projet de travail.

Je dois admettre que je n'ai pas compris quel était le véritable objectif polymorphic_allocatoret quand / pourquoi / comment je devais l'utiliser.
A titre d'exemple, le pmr::vectora la signature suivante:

namespace pmr {
    template <class T>
    using vector = std::vector<T, polymorphic_allocator<T>>;
}

Qu'est-ce que l' polymorphic_allocatoroffre? Qu'est-ce que l' std::pmr::vectoroffre aussi en ce qui concerne l'ancien std::vector? Que puis-je faire maintenant que je n'ai pas pu faire jusqu'à présent?
Quel est le véritable objectif de cet allocateur et quand dois-je l'utiliser?

skypjack
la source
1
Ils essaient de surmonter certains problèmes allocator<T>intrinsèquement. Vous verrez donc de la valeur si vous utilisez fréquemment des allocateurs.
edmz
2
Papier pertinent .
edmz

Réponses:

103

Citation de choix de cppreference:

Ce polymorphisme à l'exécution permet aux objets utilisant polymorphic_allocator de se comporter comme s'ils utilisaient différents types d'allocateur au moment de l'exécution malgré le même type d'allocateur statique

Le problème avec les allocateurs «normaux» est qu'ils changent le type de conteneur. Si vous voulez un vectoravec un allocateur spécifique, vous pouvez utiliser le Allocatorparamètre de modèle:

auto my_vector = std::vector<int,my_allocator>();

Le problème maintenant est que ce vecteur n'est pas du même type qu'un vecteur avec un allocateur différent. Vous ne pouvez pas le passer à une fonction qui nécessite un vecteur d'allocateur par défaut, par exemple, ou affecter deux vecteurs avec un type d'allocateur différent à la même variable / pointeur, par exemple:

auto my_vector = std::vector<int,my_allocator>();
auto my_vector2 = std::vector<int,other_allocator>();
auto vec = my_vector; // ok
vec = my_vector2; // error

Un allocateur polymorphe est un type d'allocateur unique avec un membre qui peut définir le comportement de l'allocateur via une répartition dynamique plutôt que via le mécanisme de modèle. Cela vous permet d'avoir des conteneurs qui utilisent une allocation spécifique et personnalisée, mais qui sont toujours d'un type commun.

La personnalisation du comportement de l'allocateur se fait en donnant à l'allocateur un std::memory_resource *:

// define allocation behaviour via a custom "memory_resource"
class my_memory_resource : public std::pmr::memory_resource { ... };
my_memory_resource mem_res;
auto my_vector = std::pmr::vector<int>(0, &mem_res);

// define a second memory resource
class other_memory_resource : public std::pmr::memory_resource { ... };
other_memory_resource mem_res_other;
auto my_other_vector = std::pmr::vector<int>(0, &mes_res_other);

auto vec = my_vector; // type is std::pmr::vector<int>
vec = my_other_vector; // this is ok -
      // my_vector and my_other_vector have same type

Le principal problème restant, à mon avis, est qu'un std::pmr::conteneur n'est toujours pas compatible avec le std::conteneur équivalent utilisant l'allocateur par défaut. Vous devez prendre certaines décisions au moment de concevoir une interface qui fonctionne avec un conteneur:

  • est-il probable que le conteneur transmis nécessite une allocation personnalisée?
  • si c'est le cas, dois-je ajouter un paramètre de modèle (pour permettre des allocateurs arbitraires) ou dois-je exiger l'utilisation d'un allocateur polymorphe?

Une solution de modèle autorise n'importe quel allocateur, y compris un allocateur polymorphe, mais présente d'autres inconvénients (taille du code généré, temps de compilation, le code doit être exposé dans le fichier d'en-tête, potentiel de "contamination de type" supplémentaire qui continue de pousser le problème vers l'extérieur). Une solution d'allocateur polymorphe, d'autre part, dicte qu'un allocateur polymorphe doit être utilisé. Cela empêche d'utiliserstd:: conteneurs qui utilisent l'allocateur par défaut et peut avoir des implications pour l'interfaçage avec le code hérité.

Comparé à un allocateur ordinaire, un allocateur polymorphe a des coûts mineurs, tels que la surcharge de stockage du pointeur memory_resource (qui est probablement négligeable) et le coût de l'envoi de fonction virtuelle pour les allocations. Le problème principal, en réalité, est probablement le manque de compatibilité avec le code hérité qui n'utilise pas d'allocateurs polymorphes.

Davmac
la source
2
Alors, la disposition binaire des std::pmr::classes est-elle très probablement différente?
Euri Pinhollow
12
@EuriPinhollow vous ne pouvez pas reinterpret_castentre un std::vector<X>et std::pmr::vector<X>, si c'est ce que vous demandez.
davmac
4
Pour les cas simples où la ressource mémoire ne dépend pas d'une variable d'exécution, un bon compilateur se dévirtualise et vous vous retrouvez avec un allocateur polymorphe sans surcoût (sauf pour stocker le pointeur qui n'est vraiment pas un problème). J'ai pensé que cela valait la peine d'être mentionné.
DeiDei
1
@ Yakk-AdamNevraumont "un std::pmr::conteneur n'est toujours pas compatible avec le std::conteneur équivalent utilisant l'allocateur par défaut" . Il n'y a pas non plus d'opérateur d'affectation défini de l'un à l'autre. En cas de doute, essayez-le: godbolt.org/z/Q5BKev (le code n'est pas exactement comme ci-dessus car gcc / clang a les classes d'allocation polymorphes dans un espace de noms "expérimental").
davmac
1
@davmac Ah, donc il n'y a pas de template<class OtherA, std::enable_if< A can be constructed from OtherA > vector( vector<T, OtherA>&& )constructeur. J'étais incertain et je ne savais pas où trouver un compilateur qui avait pmr compatible TS.
Yakk - Adam Nevraumont
33

polymorphic_allocator est à un allocateur personnalisé comme std::function à un appel direct de fonction.

Il vous permet simplement d'utiliser un allocateur avec votre conteneur sans avoir à décider, au moment de la déclaration, lequel. Donc, si vous avez une situation où plus d'un allocateur serait approprié, vous pouvez utiliserpolymorphic_allocator .

Peut-être souhaitez-vous masquer quel allocateur est utilisé pour simplifier votre interface, ou peut-être souhaitez-vous pouvoir l'échanger contre différents cas d'exécution.

Vous avez d'abord besoin d'un code nécessitant un allocateur, puis vous devez vouloir pouvoir échanger celui qui est utilisé, avant de considérer le vecteur pmr.

Yakk - Adam Nevraumont
la source
7

Un inconvénient des allocateurs polymorphes est qu'ils polymorphic_allocator<T>::pointersont toujours justes T*. Cela signifie que vous ne pouvez pas les utiliser avec des pointeurs sophistiqués . Si vous voulez faire quelque chose comme placer les éléments de a vectordans la mémoire partagée et y accéder via boost::interprocess::offset_ptrs , vous devez utiliser un ancien allocateur non polymorphe régulier pour cela.

Ainsi, bien que les allocateurs polymorphes vous permettent de modifier le comportement d' allocation sans changer le type statique d'un conteneur, ils limitent ce qu'est une allocation .

Maxpm
la source
2
C'est un point clé et une grosse déception. Arthur O'Dwyer de Vers des pointeurs de fantaisie significatifs papier explore le territoire, tout comme son livre « Maîtriser le c ++ STL 17 »
sehe
pouvez-vous donner un cas d'utilisation réel de l'utilisation de l'allocateur polymorphe?
darune le