Como apagar um elemento de std::vector por índice: olhando sob o capô
\n\nOlá, colega! Se você já programou em C++ e trabalhou com arrays dinâmicos, certamente já se deparou com a pergunta: "How do I erase an element from std::vector<> by index?" — esta é uma das perguntas mais populares no Stack Overflow, e não é à toa. À primeira vista, a tarefa parece trivial: chamar erase e pronto. Mas o que realmente acontece na memória? Por que remover do meio do vetor é caro? E como não pisar em armadilhas com iteradores?
Neste artigo, não vamos apenas mostrar como escrever v.erase(v.begin() + index). Vamos entender como essa operação funciona por baixo dos panos, quais estruturas de dados estão envolvidas e como escolher a melhor forma de remoção. Vamos nessa!
1. std::vector: que bicho é esse?
\n\nAntes de remover, vamos lembrar como std::vector é estruturado. É um array dinâmico que armazena elementos em uma área contínua de memória. Ele tem três ponteiros (ou iteradores) principais:
- \n
begin— início da memória alocada; \n end— fim da memória ocupada (após o último elemento); \n capacity— fim da memória alocada (limite até onde pode crescer sem realocação). \n
Visualmente fica assim:
\n[0] [1] [2] [3] [4] ... [capacity-1]\n ^ ^\n begin end\n\n\nQuando você remove um elemento do meio, o vetor não apenas "joga fora" o elemento — ele fisicamente desloca todos os elementos seguintes para a esquerda. Essa é a principal "dor" da operação erase.
2. Remoção por índice: sintaxe básica
\n\nA maneira mais simples de remover um elemento por índice é usar o método erase com um iterador:
#include <vector>\n#include <iostream>\n\nint main() {\n std::vector<int> v = {10, 20, 30, 40, 50};\n int index = 2; // queremos remover o terceiro elemento (30)\n\n // Verificamos se o índice está dentro dos limites\n if (index < v.size()) {\n v.erase(v.begin() + index);\n }\n\n for (int x : v) std::cout << x << " ";\n // Saída: 10 20 40 50\n return 0;\n}\n\n\nSimples, certo? Mas e se precisar remover vários elementos consecutivos? Para isso existe a sobrecarga erase(first, last):
v.erase(v.begin() + 1, v.begin() + 3); // remove os elementos nos índices 1 e 2\n\n\n3. E por baixo dos panos? Análise detalhada
\n\nAgora a parte mais interessante. Quando você chama v.erase(iterator), internamente acontece o seguinte:
- \n
- Verificação do iterador (em modo debug) — garantimos que ele é válido e aponta para um elemento do vetor. \n
- Deslocamento dos elementos — todos os elementos após o removido são copiados (ou movidos, se o tipo suportar move semantics) uma posição para a esquerda. \n
- Redução do tamanho — o contador interno
sizeé decrementado em 1. \n - Destrutor — para o elemento "final" (que agora está duplicado) o destrutor é chamado (se o tipo não for trivial). \n
Vamos ver uma implementação simplificada (pseudocódigo):
\n\ntemplate<typename T>\ntypename std::vector<T>::iterator\nvector<T>::erase(iterator pos) {\n // 1. Desloca todos os elementos após pos uma posição para a esquerda\n for (auto it = pos; it != end() - 1; ++it) {\n *it = std::move(*(it + 1));\n }\n // 2. Reduz o tamanho\n --size;\n // 3. Chama o destrutor para o último elemento\n allocator.destroy(end()); // end() agora aponta para o antigo último elemento\n return pos; // retorna um iterador para o elemento seguinte ao removido\n}\n\n\nObserve o std::move — o C++ moderno tenta evitar