Desafio C++
Invalidação de iteradores e remoção segura
Enunciado
Escreva uma função que receba um std::vector<int> e remova todos os números pares usando iteradores. Como a remoção pode invalidar iteradores, utilize a técnica de erase-remove idiom com std::remove_if e erase. Depois, imprima o vetor resultante. Em seguida, mostre um exemplo de invalidação: crie um iterador para o primeiro elemento, faça push_back e explique por que o iterador pode ter sido invalidado (comente no código).
Requisitos
- Usar
std::remove_ifcom uma lambda que retornatruepara números pares. - Chamar
erasecom o iterador retornado eend()para remover os elementos. - Imprimir o vetor após a remoção, separado por espaço.
- Incluir um comentário explicando que
push_backpode invalidar iteradores.
Código inicial
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Seu código aqui
return 0;
}
Saída esperada
1 3 5 7 9
Ver dica
O erase-remove idiom consiste em v.erase(std::remove_if(v.begin(), v.end(), pred), v.end()). A lambda deve retornar true para números pares.
Mostrar solução
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Remove todos os números pares usando erase-remove idiom
v.erase(
std::remove_if(v.begin(), v.end(), [](int n) { return n % 2 == 0; }),
v.end()
);
// Imprime o vetor resultante
for (auto it = v.begin(); it != v.end(); ++it) {
std::cout << *it << ' ';
}
std::cout << '\n';
// Exemplo de invalidação de iterador
auto it = v.begin(); // iterador para o primeiro elemento
v.push_back(11); // pode realocar o vetor e invalidar 'it'
// Após push_back, 'it' pode estar inválido. Não use *it aqui!
// Para continuar, obtenha um novo iterador: v.begin()
return 0;
}
Passo a passo
- Criamos um
std::vector<int>com valores de 1 a 10. - Chamamos
std::remove_ifpassandov.begin(),v.end()e uma lambda que retornatruepara números pares (n % 2 == 0). std::remove_ifreorganiza os elementos, movendo os ímpares para o início e retornando um iterador para a nova posição "lógica" do fim.- Em seguida,
v.eraseremove fisicamente os elementos desde esse iterador atév.end(), ajustando o tamanho do vetor. - Imprimimos o vetor resultante com um laço que usa iteradores.
- Depois, criamos um iterador
itpara o primeiro elemento e chamamosv.push_back(11). Comentamos que essa operação pode realocar o vetor e invalidarit.
Por que funciona
O erase-remove idiom é a maneira correta de remover elementos de um contêiner sequencial sem invalidar iteradores durante a remoção. std::remove_if apenas move os elementos que não satisfazem o predicado para o início, sem alterar o tamanho. O erase então remove a parte indesejada. Já o exemplo de invalidação mostra que operações que modificam o tamanho podem realocar a memória, tornando iteradores antigos inválidos — um conceito crucial para evitar bugs.
Erros comuns
- Tentar remover elementos com
erasedentro de um laço: por exemplo,for (auto it = v.begin(); it != v.end(); ++it) { if (*it % 2 == 0) v.erase(it); }invalidaite causa comportamento indefinido. Use o erase-remove idiom. - Usar
std::remove_ife esquecer oerase: o vetor mantém o mesmo tamanho, com elementos duplicados no final. Oeraseé essencial. - Desreferenciar iterador após
push_back: como mostrado,*itapóspush_backpode acessar memória liberada. Sempre obtenha um novo iterador após operações que podem realocar.
Outra forma de resolver
Se precisar remover elementos enquanto percorre e a ordem não importa, pode-se usar std::remove_if com um contêiner como std::list, que não invalida iteradores para elementos não removidos, permitindo remoção direta com erase. Porém, para std::vector, o erase-remove idiom é a solução padrão.
Saída esperada
1 3 5 7 9