Remoção de elemento de um vetor por índice: anatomia de std::vector::erase

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

Como apagar um elemento de std::vector por índice: olhando sob o capô

\n\n

Olá, 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?

\n\n

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!

\n\n

1. std::vector: que bicho é esse?

\n\n

Antes 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
    \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
\n\n

Visualmente fica assim:

\n
[0] [1] [2] [3] [4] ... [capacity-1]\n ^                        ^\n begin                   end\n
\n\n

Quando 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.

\n\n

2. Remoção por índice: sintaxe básica

\n\n

A maneira mais simples de remover um elemento por índice é usar o método erase com um iterador:

\n\n
#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\n

Simples, certo? Mas e se precisar remover vários elementos consecutivos? Para isso existe a sobrecarga erase(first, last):

\n\n
v.erase(v.begin() + 1, v.begin() + 3); // remove os elementos nos índices 1 e 2\n
\n\n

3. E por baixo dos panos? Análise detalhada

\n\n

Agora a parte mais interessante. Quando você chama v.erase(iterator), internamente acontece o seguinte:

\n\n
    \n
  1. Verificação do iterador (em modo debug) — garantimos que ele é válido e aponta para um elemento do vetor.
  2. \n
  3. 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.
  4. \n
  5. Redução do tamanho — o contador interno size é decrementado em 1.
  6. \n
  7. Destrutor — para o elemento "final" (que agora está duplicado) o destrutor é chamado (se o tipo não for trivial).
  8. \n
\n\n

Vamos ver uma implementação simplificada (pseudocódigo):

\n\n
template<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\n

Observe o std::move — o C++ moderno tenta evitar

Blogs

Book Recommendations