Comment ajouter un vecteur à un autre en C++ : ce que cache push_back

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction : ce que les documentations taisent



Imaginez : vous écrivez un serveur à forte charge ou un algorithme de traitement de données. Vous avez deux std::vector<int>, et vous devez les fusionner en un seul. Rien de plus simple, semble-t-il ? Il suffit de prendre et d'ajouter. Mais C++ ne pardonne pas l'approche superficielle. Une mauvaise méthode, et vous obtenez des copies superflues, une baisse de performance, voire un comportement indéfini.



La question « Appending a vector to a vector » sur Stack Overflow a recueilli des milliers de vues pour une bonne raison. Même les développeurs expérimentés confondent parfois push_back avec insert ou oublient la sémantique de déplacement. Dans cet article, nous allons regarder sous le capot du compilateur et comprendre comment fonctionne réellement l'ajout d'un vecteur à un autre. Vous découvrirez pourquoi std::copy peut être plus rapide que insert, et quand la sémantique de déplacement peut sauver votre projet.



La méthode la plus évidente (et dangereuse) : push_back dans une boucle



Les débutants écrivent souvent ceci :



std::vector<int> dest = {1, 2, 3};std::vector<int> src = {4, 5, 6};

for (size_t i = 0; i < src.size(); ++i) { dest.push_back(src[i]);}


Que se passe-t-il ici ? À chaque itération, push_back vérifie si la mémoire allouée est suffisante. Si ce n'est pas le cas, une réallocation se produit : l'ancien tableau est copié dans une nouvelle zone mémoire, l'ancien est supprimé. Avec trois éléments, il peut y avoir 2 à 3 réallocations. Pour les petits vecteurs, c'est imperceptible, mais avec des milliers d'éléments, c'est une catastrophe.



Problème : O(n) vérifications et copie potentielle de l'intégralité du tableau. En C++17 et versions ultérieures, le compilateur peut optimiser, mais compter là-dessus est risqué.



Que se passe-t-il sous le capot de push_back ?

  • Vérification de size() < capacity().
  • Si non, appel à reallocate(new_capacity).
  • Copie (ou déplacement) des éléments existants.
  • Suppression de l'ancien tampon.
  • Copie du nouvel élément.


Conclusion : n'utilisez jamais une boucle pour ajouter un vecteur entier, sauf si vous le faites délibérément pour un petit nombre d'éléments.



La bonne voie : std::vector::insert



La bibliothèque standard fournit une solution élégante :



std::vector<int> dest = {1, 2, 3};std::vector<int> src = {4, 5, 6};

dest.insert(dest.end(), src.begin(), src.end());


Une ligne ! Mais que se cache-t-il derrière cet appel ? insert fait ce qui suit :

  • Calcule la quantité de mémoire nécessaire : dest.size() + src.size().
  • Si la capacité actuelle est insuffisante, alloue exactement ce qui est nécessaire (ou avec une marge, selon l'implémentation).
  • Décale les éléments existants (si l'insertion n'est pas à la fin).
  • Copie (ou déplace) les nouveaux éléments depuis src.


Important : insert effectue une seule réallocation (ou aucune si l'espace est suffisant). C'est déjà mieux qu'une boucle, mais il y a toujours une copie des éléments de src.



Performances de insert

Si src n'est plus nécessaire après l'insertion, vous pouvez déplacer ses éléments en utilisant std::make_move_iterator :



dest.insert(dest.end(),            std::make_move_iterator(src.begin()),            std::make_move_iterator(src.end()));


Cela transforme la copie en déplacement. Pour std::vector<int>, il n'y a pas de différence (int est un type trivial), mais pour std::vector<std::string> ou des objets complexes, le déplacement est une broutille comparé à la copie.



Alternative : std::copy avec back_inserter



Certains développeurs préfèrent les algorithmes STL :





                
                

Blogs

Book Recommendations