95 votes

Vecteur : initialisation ou réserve ?

Je connais la taille d'un vecteur, quelle est la meilleure façon de l'initialiser ?

Option 1 :

vector<int> vec(3); //in .h
vec.at(0)=var1;     //in .cpp
vec.at(1)=var2;     //in .cpp
vec.at(2)=var3;     //in .cpp

Option 2 :

vector<int> vec;     //in .h
vec.reserve(3);      //in .cpp
vec.push_back(var1); //in .cpp
vec.push_back(var2); //in .cpp
vec.push_back(var3); //in .cpp

Je suppose que l'option 2 est meilleure que l'option 1. C'est le cas ? D'autres options ?

4voto

ShitalShah Points 2213

Comment cela fonctionne

Il s'agit d'une mise en œuvre spécifique, mais en général, la structure de données vectorielle interne aura un pointeur vers le bloc de mémoire où les éléments résident réellement. GCC et VC++ allouent 0 élément par défaut. Vous pouvez donc considérer que le pointeur de mémoire interne du vecteur est le suivant nullptr par défaut.

Lorsque vous appelez vector<int> vec(N); comme dans votre option 1, les objets N sont créés en utilisant le constructeur par défaut. Celui-ci est appelé Constructeur de remplissage .

Quand vous le faites vec.reserve(N); après Constructeur par défaut comme dans l'option 2, vous obtenez un bloc de données pour contenir 3 éléments mais aucun objet n'est créé contrairement à l'option 1.

Pourquoi choisir l'option 1

Si vous connaissez le nombre d'éléments que contiendra le vecteur et que vous risquez de laisser la plupart des éléments à leurs valeurs par défaut, vous pouvez utiliser cette option.

Pourquoi choisir l'option 2

Cette option est généralement la meilleure des deux car elle ne fait qu'allouer un bloc de données pour une utilisation future et ne se remplit pas d'objets créés par le constructeur par défaut.

0 votes

Vous avez clairement manqué la deuxième option du PO. .reserve() qui existe entièrement pour " éliminer l'expansion inutile du vecteur à mesure que vous ajoutez des éléments. " Vous semblez également sous-entendre .reserve() Si la taille du fichier est inférieure à la valeur par défaut de l'implémentation, il sera réalloué, mais ce n'est pas nécessaire et cela indiquerait une implémentation très stupide... Et ils connaissent exactement la taille. La question est de savoir s'il est plus économique d'initialiser par défaut puis de réaffecter les types de base - ou de réaffecter de manière répétée les types de base. push_back() en prenant des branches pour vérifier le .capacity() et ainsi de suite pour chaque élément. Cela semble manquer totalement et parler d'autres choses.

0 votes

Je pense que vous avez mal compris ma réponse. Je viens de la réécrire, ce qui, je l'espère, résout votre confusion.

0 votes

Je ne trouve pas ça convaincant. Je ne pense pas qu'une chance d'implémentations bizarres signifie qu'il est toujours préférable de construire par défaut de manière redondante puis de réassigner, sans prêter attention à (a) la façon dont ce n'est tout simplement pas possible pour les types qui ne peuvent être que construits et non assignés, par exemple à cause de const ou des membres de référence et (b) des benchmarks montrant que l'overhead de la réallocation (puisque nous ignorons ce que pushing doit faire d'autre) dans des bibliothèques aussi bizarres est supérieur à celui de la construction par défaut puis de la réaffectation. Je n'ai pas mesuré (b), mais il semble que personne d'autre ne l'ait fait dans ce fil de discussion... Donc, ce ne sont que des spéculations inutiles.

2voto

haberdar Points 381

A long terme, cela dépend de l'utilisation et du nombre d'éléments.

Exécutez le programme ci-dessous pour comprendre comment le compilateur réserve l'espace :

vector<int> vec;
for(int i=0; i<50; i++)
{
  cout << "size=" << vec.size()  << "capacity=" << vec.capacity() << endl;
  vec.push_back(i);
}

size est le nombre d'éléments réels et capacity est la taille réelle du tableau pour le vecteur d'éléments. Dans mon ordinateur, jusqu'à 10, les deux sont les mêmes. Mais, lorsque la taille est de 43, la capacité est de 63. Selon le nombre d'éléments, l'un ou l'autre peut être meilleur. Par exemple, augmenter la capacité peut être coûteux.

0 votes

Je ne vois pas en quoi cela est pertinent. Vous parlez de réallocation lorsque la taille augmente. L'OP parle d'une taille connue et, dans les deux cas, évite de réallouer (en initialisant ou en réservant de l'espace).

2voto

Apollys Points 726

Comme il semble que 5 ans se soient écoulés et qu'une mauvaise réponse soit toujours acceptée, et que la réponse la plus votée soit complètement inutile (on a raté la forêt pour les arbres), je vais ajouter une vraie réponse.

Méthode n° 1 nous passons un paramètre de taille initiale dans le vecteur, appelons-le n . Cela signifie que le vecteur est rempli de n qui seront initialisés à leur valeur par défaut. Par exemple, si le vecteur contient int il sera rempli de n zéros.

Méthode n° 2 Nous créons d'abord un vecteur vide. Ensuite, nous réservons un espace pour n éléments. Dans ce cas, nous ne créons jamais l'élément n et donc nous n'effectuons jamais d'initialisation des éléments du vecteur. Comme nous prévoyons d'écraser immédiatement les valeurs de chaque élément, l'absence d'initialisation ne nous causera aucun tort. D'un autre côté, comme nous en avons fait moins dans l'ensemble, cette option serait la meilleure*.

* meilleur - définition réelle : jamais pire . Il est toujours possible qu'un compilateur intelligent comprenne ce que vous essayez de faire et l'optimise pour vous.


Conclusion : utiliser la méthode n°2.

1 votes

Et quel est le problème avec la réponse acceptée ? Il est aussi dit que la première méthode crée n avec la valeur par défaut et la seconde ne réserve que la mémoire. En plus de cela, le premier a le surcoût de l'initialisation par défaut et de la vérification des limites, le second a le surcoût de la vérification des limites. push_back logique (chaning la taille actuelle et la vérification si la mémoire réservée doit être augmentée).

2 votes

La méthode n° 2 est meilleure, c'est-à-dire qu'elle n'est jamais pire, ce qui serait contredit par les éléments suivants votre propre autre, bien meilleure réponse un an plus tard ! J'ai vraiment aimé celui-là. Je ne suis pas sûr pour celui-ci. Il est possible qu'un compilateur pas si malin ne réalise pas qu'il n'a pas besoin de vérifier la capacité par rapport à la taille à chaque fois et qu'il le fasse, ce qui fait perdre du temps de chargement, de test et de branchement ; alors que l'initialisation puis l'affectation n'ont jamais à vérifier la capacité. Comme votre autre réponse l'a montré, l'analyse comparative avec un nombre réaliste d'éléments est la seule façon d'évaluer cela.

1 votes

Cette réponse devrait probablement être supprimée par l'auteur, en faveur de leur propre autre, bien meilleure réponse .

1voto

Bo Persson Points 42821

Une autre option est de faire confiance à votre compilateur(tm) et d'effectuer les opérations suivantes push_back sans appeler reserve d'abord. Il doit allouer de l'espace lorsque vous commencez à ajouter des éléments. Peut-être le fait-il aussi bien que vous ?

Il est "préférable" d'avoir un code plus simple qui fait le même travail.

0 votes

Votre idée contredirait la réalité. Voir l'exemple donné à : fr.cppreference.com/w/cpp/container/vector/reserve

0 votes

C'est une exemple . D'autres implémentations pourraient utiliser une première allocation de 16 octets, car elles savent que c'est la taille minimale d'allocation du tas de toute façon. Et cela fonctionnerait alors pour 4 push_backs.

0 votes

Merci pour un autre exemple dans votre réponse au commentaire.

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