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
Leia também
Trabalhando com datas e horas em Swift: guia completo
Desenvolvedor Go: salário, perspectivas e futuro em 2024
Filas de tarefas em Kotlin: criação, gerenciamento e otimização
Como mockar métodos void no Mockito: guia para iniciantes com exemplos
O operador secreto --> em C++: uma piada que se tornou lenda