Como usar std::prev e std::next no dia a dia
Antecessor e sucessor de um iterador são funções básicas da biblioteca padrão do C++ que todo desenvolvedor acaba usando, mas nem sempre da forma mais eficiente. O que muita gente não sabe é que essas funções têm diferenças sutis que podem transformar código em problemas de desempenho ou bugs silenciosos. std::prev(it, n) retorna um iterador apontando para o elemento anterior à posição `it`. O parâmetro `n` é opcional e padrão 1. std::next(it, n) funciona de forma equivalente para frente. Ambas estão no cabeçalho `
Onde aprender antecessor e sucessor atividade
A documentação oficial está no cppreference.com, mas o material em português é escasso. O Guia Prático do C++ do professor Marcelo tem uma seção decente sobre iteradores, e o site DevMedia também tem artigos introdutórios. Para conteúdo avançado, recomendo o capítulo 11 do livro "Effective Modern C++", do Scott Meyers, que aborda armadilhas específicas dessas funções. No mercado, você vai encontrar essas funções usadas em código legado e em código novo com intensidades diferentes. Em sistemas embarcados, por exemplo, costuma-se evitar std::next porque a versão manual com operador `++` é mais previsível em tempo de execução. Já em aplicações de desktop e servidores, a diferença de performance é insignificante na maioria dos casos.
Um problema real que eu encontrei: em uma list encadeada personalizada, usei std::prev(list.begin()) e o compilador não reclamou, mas o comportamento era indefinido. O iterador `begin()` de uma lista vazia ou a passagem por prev do início resulta em acesso inválido. A correção foi adicionar uma verificação explícita antes de chamar a função: if (it != container.begin()) { auto anterior = std::prev(it); }
Isso parece óbvio, mas em code reviews já vi essa falha aparecer em produção porque o desenvolvedor assumiu que o iterador nunca estaria no início.
Pegadinhas que iniciantes esquecem
A primeira é: std::prev e std::next funcionam apenas com iteradores de entrada, avanço, bidirecionais e aleatórios. Não adianta tentar usá-las com iteradores de output. O compilador pode não gerar erro de compilação imediato em alguns cenários, mas o comportamento será indefinido. Testei isso com um streambuf_iterator e o programa compilou sem warning algum. Quebrou só na execução. A segunda pegadinha é mais sutil. Quando você passa um valor negativo para o parâmetro `n` de std::next, ele se comporta como std::prev. E vice-versa. Isso é documentado, mas poucos programadores lembram na hora da pressão. Já vi alguém debugar três horas achando que o iterador estava se movendo para frente quando na verdade estava indo para trás por causa de um valor de `n` errado.
Para iteradores de acesso aleatório (vetores, arrays), ambas as funções operam em O(1). Para iteradores bidirecionais (listas, maps), a complexidade é O(n) porque precisam avançar ou retroceder passo a passo. Se você está trabalhando com uma coleção grande e precisa buscar posições específicas dentro de um loop, considerar usar índice numérico pode ser mais rápido do que confiar em std::next repetidamente.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Exemplo prático
Vamos supor um vetor de inteiros onde queremos obter o elemento anterior e o posterior de cada posição, ignorando as bordas: std::vector
for (auto it = v.begin() + 1; it != v.end() - 1; ++it) { auto anterior = std::prev(it);
auto posterior = std::next(it); // usar anterior e posterior
} Esse padrão é comum em processamento de signals, análise de dados e algoritmos de sliding window. Note que a condição do loop já exclui as bordas, o que evita o problema citado anteriormente.
Alternativas e quando não usar
Se você precisa apenas de acesso pontual e trabalha com vetores, usar índice diretamente (`v[it - v.begin() - 1]`) é mais legível e ligeiramente mais rápido. A vantagem de std::prev e std::next aparece quando você lida com containers que não suportam acesso aleatório, como listas e mapas. Em C++20, houve a introdução de std::ranges::prev e std::ranges::next, que operam sobre ranges em vez de iteradores soltos. Para projetos modernos, vale a pena migrar para essa versão, especialmente se você já está usando outras funcionalidades do C++20 ranges.
O principal downside dessas funções é a falta de feedback em tempo de compilação quando o iterador está em uma posição inválida. O compilador não sabe se `std::prev(begin())` é um erro lógico. Ferramentas como sanitizadores de memória (-fsanitize=address) ajudam a capturar esses erros durante os testes, mas não previnem o problema na origem. Uma boa prática é sempre validar a posição do iterador antes de chamar essas funções, ou encapsulá-las em wrappers que lancem exceções em cenários de borda. Isso adiciona uma linha a mais de código, mas evita horas de debug.