51 votes

Est-ce que std::vector *doit* déplacer les objets quand sa capacité augmente ? Ou bien, les allocateurs peuvent-ils "réallouer" ?

A question différente a inspiré la pensée suivante :

Fait std::vector<T> ont pour déplacer tous les éléments lorsqu'elle augmente sa capacité ?

D'après ce que je comprends, le comportement standard est que l'allocateur sous-jacent demande un morceau entier de la nouvelle taille, puis déplace tous les anciens éléments, détruit les anciens éléments et désalloue l'ancienne mémoire.

Ce comportement semble être la seule solution correcte possible compte tenu de l'interface standard de l'allocateur. Mais je me demandais s'il ne serait pas judicieux de modifier l'allocateur pour qu'il propose une fonction reallocate(std::size_t) qui renverrait un pair<pointer, bool> et pourrait correspondre à la base de données realloc() ? L'avantage de cette solution serait que, dans le cas où le système d'exploitation peut réellement étendre la mémoire allouée, alors aucun déplacement ne devrait avoir lieu. Le booléen indiquerait si la mémoire a été déplacée.

( std::realloc() n'est peut-être pas le meilleur choix, car nous n'avons pas besoin de copier les données si nous ne pouvons pas les étendre. Donc, en fait, nous préférons quelque chose comme extend_or_malloc_new() . Edit : Peut-être qu'un is_pod -La spécialisation basée sur les traits nous permettrait d'utiliser le réel realloc y compris sa copie bit à bit. Mais pas en général).

Cela semble être une occasion manquée. Dans le pire des cas, vous pouvez toujours implémenter reallocate(size_t n) comme return make_pair(allocate(n), true); donc il n'y aurait pas de pénalité.

Y a-t-il un problème qui rende cette fonctionnalité inappropriée ou indésirable pour le C++ ?

Le seul conteneur qui pourrait en profiter est peut-être std::vector mais là encore, c'est un conteneur assez utile.


Mise à jour : Un petit exemple pour clarifier. Current resize() :

pointer p = alloc.allocate(new_size);

for (size_t i = 0; i != old_size; ++i)
{
  alloc.construct(p + i, T(std::move(buf[i])))
  alloc.destroy(buf[i]);
}
for (size_t i = old_size; i < new_size; ++i)
{
  alloc.construct(p + i, T());
}

alloc.deallocate(buf);
buf = p;

Nouvelle mise en œuvre :

pair<pointer, bool> pp = alloc.reallocate(buf, new_size);

if (pp.second) { /* as before */ }
else           { /* only construct new elements */ }

42voto

Howard Hinnant Points 59526

Lorsque std::vector<T> n'a plus de capacité, il a pour allouer un nouveau bloc. Vous avez correctement exposé les raisons.

IMO il serait Il n'est pas logique d'augmenter l'interface de l'allocateur. Deux d'entre nous ont essayé de le faire pour C++11 et nous n'avons pas réussi à obtenir le soutien nécessaire : [1] [2]

J'ai acquis la conviction que pour que cela fonctionne, une API supplémentaire de niveau C serait nécessaire. Je n'ai pas réussi à obtenir le soutien nécessaire à ce sujet non plus : [3]

9voto

Dans la plupart des cas, realloc ne sera pas étendre la mémoire, mais plutôt allouer un bloc séparé et déplacer son contenu. Ce point a été pris en compte lors de la définition du C++ en premier lieu, et il a été décidé que l'interface actuelle est plus simple et non moins efficace dans le cas courant.

Dans la vie réelle, il y a en fait peu de cas où realloc est capable de se développer . Dans toute implémentation où malloc a des tailles de pool différentes, il y a de fortes chances que la nouvelle taille (rappelez-vous que vector les tailles doivent croître géométriquement) tomberont dans un pool différent. Même dans le cas de gros morceaux qui ne sont alloués à partir d'aucun pool de mémoire, il ne pourra croître que si les adresses virtuelles de la plus grande taille sont libres.

Notez que si realloc peut parfois se développer la mémoire sans déménagement mais au moment où realloc complète il pourrait déjà avoir déplacé (déplacement par bit) de la mémoire, et que le binaire déplacer provoquera un comportement indéfini pour tous les types non-POD. Je ne connais pas d'implémentation d'allocateur (POSIX, *NIX, Windows) où vous pouvez demander au système s'il sera capable de se développer mais cela échouerait s'il faut déménagement .

0voto

Maxim Yegorushkin Points 29380

Oui, vous avez raison, l'interface standard de l'allocateur ne fournit pas d'optimisations pour les types memcpy.

Il a été possible de déterminer si un type peut être memcpy en utilisant la bibliothèque boost type traits (je ne suis pas sûr qu'elle le fournisse d'emblée ou qu'il faille construire un discriminateur de type composite basé sur ceux de boost).

Bref, pour profiter de realloc() il faudrait probablement créer un nouveau type de conteneur qui puisse explicitement tirer parti de cette optimisation. Avec l'interface standard actuelle de l'allocateur, cela ne semble pas être possible.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X