Como chamar apagar com um iterador reverso

181

Estou tentando fazer algo assim:

for ( std::list< Cursor::Enum >::reverse_iterator i = m_CursorStack.rbegin(); i != m_CursorStack.rend(); ++i )
{
    if ( *i == pCursor )
    {
        m_CursorStack.erase( i );
        break;
    }
}

No entanto, apagar exige um iterador e não um iterador reverso. existe uma maneira de converter um iterador reverso em um iterador regular ou outra maneira de remover esse elemento da lista?

0xC0DEFACE
fonte
17
Além disso, ao escrever loops como esses, não calcule repetidamente o iterador final, como você faz aqui i != m_CursorStack.rend(). Em vez disso, escreva i = m_CursorStack.rbegin(), end = m_CursorStack.rend(); i != end;. Ou seja, inicialize um iterador que você pode manter para comparação repetida - supondo que a posição final não seja alterada como efeito colateral do seu corpo em loop.
seh
Parece-me que a pergunta óbvia aqui seria por que você está fazendo isso. O que você ganha ao percorrer a lista ao contrário? O que você ganha escrevendo esse código em vez de usar std::remove?
Jerry Coffin
E um iterador em um std :: list ainda é válido para incrementar depois que o elemento ao qual ele se refere foi apagado?
21609 Steve Jessop #
3
Eu só quero remover 1 elemento, portanto, o 'break;', usando 'remove', livrar-se-á de qualquer partida que demore mais e não faça o que eu quero. O elemento que eu quero remover nesse caso em particular quase sempre será o final da lista ou muito próximo a ela; portanto, a iteração inversa também é mais rápida e mais adequada ao problema.
0xC0DEFACE
4
stackoverflow.com/a/2160581/12386 Isso diz que os designers especificamente não definem a implementação porque você, como o usuário não deveria saber ou se importar, mas o @seh acima espera que, magicamente, apenas saibamos que o rend () é calculado e caro.
22818 stu

Respostas:

181

Depois de mais algumas pesquisas e testes, encontrei a solução. Aparentemente, de acordo com o padrão [24.4.1 / 1], a relação entre i.base () ei é:

&*(reverse_iterator(i)) == &*(i - 1)

(de um artigo do Dr. Dobbs ):

texto alternativo

Portanto, você precisa aplicar um deslocamento ao obter a base (). Portanto, a solução é:

m_CursorStack.erase( --(i.base()) );

EDITAR

Atualizando para C ++ 11.

reverse_iterator ipermanece inalterado:

m_CursorStack.erase( std::next(i).base() );

reverse_iterator ié avançado:

std::advance(i, 1);
m_CursorStack.erase( i.base() );

Acho isso muito mais claro que minha solução anterior. Use o que você precisar.

0xC0DEFACE
fonte
27
Você deve tomar nota de um pouco mais do artigo que você citou - para ser portátil, a expressão deveria ser m_CursorStack.erase( (++i).base())(cara, fazer essas coisas com iteradores reversos faz minha cabeça doer ...). Também deve ser observado que o artigo DDJ é incorporado ao livro "Effective STL" de Meyer.
22711 Michael Burr
8
Acho esse diagrama mais confuso do que útil. Desde rbegin, ri e rend são todos realmente apontando para o elemento para a direita do que eles são atraídos para estar apontando para. O diagrama mostra qual elemento você acessaria se você os *utilizasse, mas estamos falando sobre qual elemento você apontaria se os utilizasse base, que é um elemento à direita. Não sou muito fã das soluções --(i.base())ou (++i).base(), pois elas modificam o iterador. Eu prefiro o (i+1).base()que funciona também.
mgiuca
4
Os iteradores reversos são mentirosos. Quando diferido, um iterador reverso retorna o elemento antes dele . Veja aqui
bobobobo
4
Apenas para ficar absolutamente claro, essa técnica ainda não pode ser usada em um loop for normal (onde o iterador é incrementado da maneira normal). Veja stackoverflow.com/questions/37005449/…
logidelic
1
m_CursorStack.erase ((++ i) .base ()) parece que seria um problema se ++ fizesse com que você passasse o último elemento. você pode chamar apagar no final ()?
22818 stu
16

Observe que m_CursorStack.erase( (++i).base())pode ser um problema se usado em um forloop (consulte a pergunta original), pois altera o valor de i. A expressão correta ém_CursorStack.erase((i+1).base())

Andrey
fonte
4
Você precisaria criar uma cópia do iterador e fazer iterator j = i ; ++j, porque i+1não funciona em um iterador, mas essa é a idéia certa
bobobobo
3
@obobobo, você pode usar o m_CursorStack.erase(boost::next(i).base())Boost. ou em C ++ 11m_CursorStack.erase(std::next(i).base())
alfC
12

... ou outra maneira de remover esse elemento da lista?

Isso requer o -std=c++11sinalizador (para auto):

auto it=vt.end();
while (it>vt.begin())
{
    it--;
    if (*it == pCursor) //{ delete *it;
        it = vt.erase(it); //}
}
slashmais
fonte
Funciona um charme :)
Den-Jason
@GaetanoMendola: por quê?
Slashmais 11/05/19
3
Quem garante que os iteradores de uma lista são ordenados?
Gaetano Mendola
7

Engraçado que ainda não há uma solução correta nesta página. Portanto, o seguinte é o correto:

No caso do iterador direto, a solução é direta:

std::list< int >::iterator i = myList.begin();
while ( ; i != myList.end(); ) {
  if ( *i == to_delete ) {
    i = myList.erase( i );
  } else {
    ++i;
  } 
}

No caso do iterador reverso, você precisa fazer o mesmo:

std::list< int >::reverse_iterator i = myList.rbegin();
while ( ; i != myList.rend(); ) {
  if ( *i == to_delete ) {
    i = decltype(i)(myList.erase( std::next(i).base() ));
  } else {
    ++i;
  } 
}

Notas:

  • Você pode construir um reverse_iterator partir de um iterador
  • Você pode usar o valor de retorno de std::list::erase
Gaetano Mendola
fonte
Esse código funciona, mas explique por que o próximo é usado e como é seguro converter um iterador direto em um iterador reverso sem que o mundo entre em colapso
Lefteris E
1
@ LefterisE Isso não é um elenco. Ele cria um novo iterador reverso a partir de um interador. Este é o construtor normal do iterador reverso.
Šimon Tóth 16/06
3

Enquanto estiver usando o reverse_iterator's base()método e diminuindo o resultado funciona aqui, é importante notar que reverse_iterators não é dado o mesmo estatuto que regulares iterators. Em geral, você deve preferir iterators regulares para reverse_iterators (e também para const_iteratores e const_reverse_iterators), por motivos precisos como este. Veja o Diário do Doutor Dobbs para uma discussão aprofundada sobre o porquê.

Adam Rosenfield
fonte
3
typedef std::map<size_t, some_class*> TMap;
TMap Map;
.......

for( TMap::const_reverse_iterator It = Map.rbegin(), end = Map.rend(); It != end; It++ )
{
    TMap::const_iterator Obsolete = It.base();   // conversion into const_iterator
    It++;
    Map.erase( Obsolete );
    It--;
}
Nismo
fonte
3

E aqui está o trecho de código para converter o resultado de apagar novamente em um iterador reverso, a fim de apagar um elemento em um contêiner enquanto itera no reverso. Um pouco estranho, mas funciona mesmo ao apagar o primeiro ou o último elemento:

std::set<int> set{1,2,3,4,5};

for (auto itr = set.rbegin(); itr != set.rend(); )
{    
    if (*itr == 3)
    {
        auto it = set.erase(--itr.base());
        itr = std::reverse_iterator(it);            
    }
    else
        ++itr;
}
Etham
fonte
2

Se você não precisar apagar tudo à medida que avança, para resolver o problema, use o idioma apagar-remover:

m_CursorStack.erase(std::remove(m_CursorStack.begin(), m_CursorStack.end(), pCursor), m_CursorStack.end());

std::removetroca todos os itens no contêiner que correspondem pCursorao final e retorna um iterador para o primeiro item de correspondência. Então oerase uso de um intervalo será apagado da primeira partida e chegará ao fim. A ordem dos elementos não correspondentes é preservada.

Isso pode funcionar mais rápido se você estiver usando um std::vector , onde a exclusão no meio do conteúdo pode envolver muitas cópias ou movimentação.

Ou, é claro, as respostas acima que explicam o uso de reverse_iterator::base()são interessantes e vale a pena conhecer, para resolver o problema exato declarado, eu argumentaria que std::removeé um ajuste melhor.

gavinbeatty
fonte
1

Só queria esclarecer uma coisa: Em alguns dos comentários e respostas acima, a versão portátil para apagar é mencionada como (++ i) .base (). No entanto, a menos que esteja faltando alguma coisa, a instrução correta é (++ ri) .base (), significando que você 'incrementa' o reverse_iterator (não o iterador).

Encontrei ontem uma necessidade de fazer algo semelhante e este post foi útil. Obrigado a todos.

user1493570
fonte
0

Para complementar as respostas de outras pessoas e porque eu me deparei com essa pergunta enquanto pesquisava sobre std :: string sem muito sucesso, aqui vai uma resposta com o uso de std :: string, std :: string :: erase e std :: reverse_iterator

Meu problema foi apagar o nome de arquivo de uma imagem de uma string de nome de arquivo completa. Foi originalmente resolvido com std :: string :: find_last_of, mas eu pesquiso uma maneira alternativa com std :: reverse_iterator.

std::string haystack("\\\\UNC\\complete\\file\\path.exe");
auto&& it = std::find_if( std::rbegin(haystack), std::rend(haystack), []( char ch){ return ch == '\\'; } );
auto&& it2 = std::string::iterator( std::begin( haystack ) + std::distance(it, std::rend(haystack)) );
haystack.erase(it2, std::end(haystack));
std::cout << haystack;  ////// prints: '\\UNC\complete\file\'

Isso usa algoritmos, iteradores e cabeçalhos de string.

fmmarques
fonte
0

O iterador reverso é bastante difícil de usar. Então, apenas usei o iterador geral. 'r' Começa do último elemento. Quando encontrar algo para apagar. apague-o e retorne o próximo iterador. por exemplo, quando excluir o terceiro elemento, ele apontará o quarto elemento atual. e novo terceiro. Portanto, deve ser diminuído 1 para mover para a esquerda

void remchar(string& s,char c)
{      
    auto r = s.end() - 1;
    while (r >= s.begin() && *r == c)
    {
        r = s.erase(r);
        r -= 1;
    }
}
Mark Yang
fonte