Vector: initialisation ou réserve?

Je connais la taille d’un vecteur, quelle est la meilleure procédure pour l’initialiser?

Option 1

vector 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 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 1. est-ce? autres options?

Les deux variantes ont une sémantique différente, c’est-à-dire que vous comparez des pommes et des oranges.

La première vous donne un vecteur de n valeurs initialisées par défaut, la seconde variante réserve la mémoire, mais ne les initialise pas.

Choisissez ce qui correspond le mieux à vos besoins, c.-à-d. Ce qui est “meilleur” dans une situation donnée.

Le “meilleur” moyen serait:

 vector vec = {var1, var2, var3}; 

disponible avec un compilateur compatible C ++ 11.

Vous ne savez pas exactement ce que vous voulez dire en faisant des choses dans un en-tête ou des fichiers d’implémentation. Un monde mutable est un non-non pour moi. S’il s’agit d’un membre de classe, il peut être initialisé dans la liste d’initialisation du constructeur.

Sinon, l’option 1 serait généralement utilisée si vous savez combien d’éléments vous allez utiliser et si les valeurs par défaut (0 pour int) seraient utiles.
Utiliser ici signifie que vous ne pouvez pas garantir que l’index est valide. Une telle situation est alarmante. Même si vous êtes en mesure de détecter les problèmes de manière fiable, il est beaucoup plus simple d’utiliser push_back et de ne plus vous soucier des index.

Dans le cas de l’option 2, il n’y a généralement aucune différence de performance, que vous réserviez de la mémoire ou non, il est donc plus simple de ne pas réserver *. À moins que le vecteur ne contienne des types de copie très coûteux (et ne permettent pas un déplacement rapide en C ++ 11), ou que la taille du vecteur sera énorme.


* Extrait du style et de la technique C ++ de Stroustrups:

Les gens s’inquiètent parfois du coût de la croissance de std :: vector. Je m’inquiétais de cela et utilisais reserve () pour optimiser la croissance. Après avoir mesuré mon code et rencontré des difficultés à trouver les avantages de reserve () dans de vrais programmes, je me suis arrêté à l’utiliser sauf pour éviter l’invalidation de l’iterator (un cas rare dans mon code). Encore une fois: mesurez avant d’optimiser.

L’option 2 est meilleure, car la réserve ne doit réserver que de la mémoire (3 * sizeof (T)), tandis que la première option appelle le constructeur du type de base pour chaque cellule du conteneur.

Pour les types de type C, ce sera probablement la même chose.

Bien que vos exemples soient essentiellement les mêmes, il se peut que lorsque le type utilisé ne soit pas un int le choix vous soit pris. Si votre type n’a pas de constructeur par défaut, ou si vous devez reconstruire chaque élément ultérieurement, j’utiliserais reserve . Il suffit de ne pas tomber dans le piège que j’ai fait et d’utiliser reserve puis l’ operator[] pour l’initialisation!

Constructeur

 std::vector myVec(numberOfElementsToStart); int size = myVec.size(); int capacity = myVec.capacity(); 

Dans ce premier cas, l’utilisation du constructeur, de size et numberOfElementsToStart sera égale et leur capacity sera supérieure ou égale à celle-ci.

Considérez myVec comme un vecteur contenant un certain nombre d’éléments de MyType auxquels vous pouvez accéder et modifier, push_back(anotherInstanceOfMyType) l’appenda à la fin du vecteur.


réserve

 std::vector myVec; myVec.reserve(numberOfElementsToStart); int size = myVec.size(); int capacity = myVec.capacity(); 

Lorsque vous utilisez la fonction de reserve , la size sera 0 jusqu’à ce que vous ajoutiez un élément au tableau et que la capacity soit égale ou supérieure à numberOfElementsToStart .

Considérez myVec comme un vecteur vide auquel de nouveaux éléments peuvent être ajoutés à l’aide de push_back sans surcharge pour au moins les premiers éléments numberOfElementsToStart .

Une autre option consiste à faire confiance à votre compilateur ™ et à effectuer le push_back s sans appeler en premier la reserve . Il doit allouer de l’espace lorsque vous commencez à append des éléments. Peut-être le fait-il aussi bien que vous le feriez?

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

À 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 de l’espace:

 vector 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 la capacité est la taille réelle du tableau à un vecteur d'implantation. Dans mon ordinateur, jusqu'à 10 ans, 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'une ou l'autre peut être meilleure. Par exemple, augmenter la capacité peut être coûteux.

Si vous connaissez déjà le nombre exact d’éléments contenus dans le vecteur, utilisez l’option 1. Ceci éliminera les extensions inutiles du vecteur lorsque vous ajoutez des éléments. Même si le temps d’amortissement nécessaire pour faire de l’expansion est O (1), cela fait une différence de performance significative si vous traitez de gros vecteurs ou de nombreux vecteurs dans une boucle. Encore une fois, il est préférable d’éviter une optimisation prématurée.

L’option 2 serait très similaire à l’option 1 sur la plupart des systèmes car la plupart des implémentations vectorielles réservent 0 éléments par défaut (GCC et VC ++, par exemple). Donc, c’est juste un appel supplémentaire que vous évitez. Cependant, si vector implémente des réserves n éléments par défaut (par exemple, l’équivalent .Net du vecteur le fait), vous libérez une allocation précédente et effectuez une ré-allocation. C’est pourquoi je préférerais l’option 1 en général.

L’option 2 est utile dans une situation: si vous ne connaissiez que le nombre d’éléments que votre vecteur contiendra, utilisez l’option 2. Dans ce cas, vous ne pouvez pas utiliser l’option 1, mais l’option 2 vous donnerait un bon résultat. .

Comme il semble que 5 ans se soient écoulés et qu’une réponse fausse soit toujours acceptée, et que la réponse la plus relevée est totalement inutile (la forêt manque pour les arbres), je vais append une vraie réponse.

Méthode n ° 1 : nous passons un paramètre de taille initial dans le vecteur (appelons-le n . Cela signifie que le vecteur est rempli de n éléments, qui seront initialisés à leur valeur par défaut. Par exemple, si le vecteur contient int s, il sera être rempli avec 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 les n éléments et nous n’effectuons donc jamais d’initialisation des éléments du vecteur. Comme nous prévoyons de remplacer immédiatement les valeurs de chaque élément, l’absence d’initialisation ne nous fera pas de mal. Par contre, comme nous avons fait moins globalement, ce serait la meilleure option *.

* mieux – vraie définition: jamais pire . Il est toujours possible qu’un compilateur intelligent détermine ce que vous essayez de faire et l’optimise pour vous.


Conclusion : utilisez la méthode n ° 2.