Como adicionar um vetor a outro em C++: o que o push_back esconde

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: sobre o que as documentações silenciam



Imagine: você está escrevendo um servidor de alta carga ou um algoritmo de processamento de dados. Você tem dois std::vector<int> e precisa combiná-los em um. Parece simples, não? Basta pegar e adicionar. Mas C++ não perdoa abordagens superficiais. Uma maneira errada — e você obtém cópias extras, queda de desempenho ou até comportamento indefinido.



A pergunta «Appending a vector to a vector» no Stack Overflow acumulou milhares de visualizações não por acaso. Até desenvolvedores experientes às vezes confundem push_back com insert ou esquecem a move semantics. Neste artigo, vamos olhar por baixo do capô do compilador e entender como realmente funciona a adição de um vetor a outro. Você descobrirá por que std::copy pode ser mais rápido que insert e quando a move semantics salvará seu projeto.



A maneira mais óbvia (e perigosa): push_back em loop



Iniciantes frequentemente escrevem assim:



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]);}


O que acontece aqui? A cada iteração, push_back verifica se há memória suficiente alocada. Se não, ocorre uma realocação: o array antigo é copiado para uma nova área de memória, e o antigo é deletado. Com três elementos, pode haver 2–3 realocações. Para vetores pequenos, isso é imperceptível, mas com milhares de elementos — é um desastre.



Problema: O(n) verificações e potenciais cópias de todo o array. Em C++17 e posteriores, o compilador pode otimizar, mas confiar nisso é arriscado.



O que acontece por baixo dos panos no push_back?

  • Verificação de size() < capacity().
  • Se não — chamada de reallocate(new_capacity).
  • Cópia (ou movimentação) dos elementos existentes.
  • Exclusão do buffer antigo.
  • Cópia do novo elemento.


Conclusão: nunca use um loop para adicionar um vetor inteiro, a menos que você o faça conscientemente para um pequeno número de elementos.



O caminho correto: std::vector::insert



A biblioteca padrão fornece uma solução elegante:



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

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


Uma linha! Mas o que está escondido por trás dessa chamada? insert faz o seguinte:

  • Calcula a quantidade necessária de memória: dest.size() + src.size().
  • Se a capacidade atual for insuficiente — aloca exatamente o necessário (ou com margem, depende da implementação).
  • Desloca os elementos existentes (se não estiver inserindo no final).
  • Copia (ou move) os novos elementos de src.


Importante: insert faz uma realocação (ou nenhuma, se houver espaço suficiente). Isso já é melhor que o loop, mas ainda há cópia dos elementos de src.



Desempenho do insert

Se src não for mais necessário após a inserção, você pode mover seus elementos usando std::make_move_iterator:



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


Isso transforma a cópia em movimentação. Para std::vector<int> não há diferença (int é um tipo trivial), mas para std::vector<std::string> ou objetos complexos, a movimentação é muito mais barata que a cópia.



Alternativa: std::copy com back_inserter



Alguns desenvolvedores preferem algoritmos STL:





                
                

Blogs

Book Recommendations