Come chiamare cancellare con un iteratore inverso


181

Sto cercando di fare qualcosa del genere:

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

Tuttavia, la cancellazione richiede un iteratore e non un iteratore inverso. c'è un modo per convertire un iteratore inverso in un iteratore normale o un altro modo per rimuovere questo elemento dall'elenco?


17
Per inciso, quando si scrivono loop come questi, non calcolare ripetutamente l'iteratore finale come si fa qui i != m_CursorStack.rend(). Invece, scrivi i = m_CursorStack.rbegin(), end = m_CursorStack.rend(); i != end;. Cioè, inizializza un iteratore che puoi tenere in giro per un confronto ripetuto, supponendo che la posizione finale non cambierà come effetto collaterale del tuo loop.
seh

Mi sembra che l'ovvia domanda qui sarebbe perché lo stai facendo affatto. Cosa guadagni attraversando la lista al contrario? Cosa guadagni scrivendo questo codice tu stesso invece di usarlo std::remove?
Jerry Coffin,

E un iteratore in una lista std :: è ancora valido per incrementare dopo che l'elemento a cui si riferisce è stato cancellato?
Steve Jessop,

3
Voglio solo rimuovere 1 elemento, quindi "break;", usando "remove" mi sbarazzerei di qualsiasi partita che impiega più tempo e non fa quello che voglio. L'elemento che voglio rimuovere in questo caso particolare sarà quasi sempre la fine dell'elenco o molto vicino ad esso, quindi l'iterazione al contrario è anche più veloce e più adatta al problema.
0xC0DEFACE

4
stackoverflow.com/a/2160581/12386 Questo dice che i progettisti non definiscono specificamente l'implementazione perché tu come utente non dovresti sapere o preoccuparti, ma @seh sopra si aspetta che noi magicamente sappia solo che rend () è calcolato e costoso.
martedì

Risposte:


181

Dopo qualche altra ricerca e test ho trovato la soluzione. Apparentemente secondo lo standard [24.4.1 / 1] la relazione tra i.base () e i è:

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

(da un articolo del Dr. Dobbs ):

testo alternativo

Quindi è necessario applicare un offset quando si ottiene la base (). Pertanto la soluzione è:

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

MODIFICARE

Aggiornamento per C ++ 11.

reverse_iterator iè invariato:

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

reverse_iterator iè avanzato:

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

Lo trovo molto più chiaro della mia precedente soluzione. Usa quello che ti serve.


27
Dovresti prendere nota di un po 'di più dell'articolo che hai citato - per essere portatile l'espressione dovrebbe essere m_CursorStack.erase( (++i).base())(amico, fare questa roba con iteratori inversi mi fa male alla testa ...). Va anche notato che l'articolo del DDJ è incorporato nel libro "Effective STL" di Meyer.
Michael Burr,

8
Trovo questo diagramma più confuso che utile. Poiché rbegin, ri e rend puntano effettivamente tutti sull'elemento a destra di ciò a cui sono disegnati. Il diagramma mostra a quale elemento accederesti se tu *li stessi, ma stiamo parlando di quale elemento dovresti puntare se tu baseli, che è un elemento a destra. Non sono un fan delle soluzioni --(i.base())o (++i).base()poiché mutano l'iteratore. Preferisco anche quello (i+1).base()che funziona.
mgiuca,

4
Gli iteratori inversi sono bugiardi .. quando rinviati, un iteratore inverso restituisce l'elemento prima di esso . Vedi qui
bobobobo,

4
Per essere assolutamente chiari, questa tecnica non può ancora essere utilizzata in un normale ciclo per (in cui l'iteratore viene incrementato in modo normale). Vedere stackoverflow.com/questions/37005449/...
logidelic

1
m_CursorStack.erase ((++ i) .base ()) sembra che sarebbe un problema se ++ ti portasse a superare l'ultimo elemento. puoi chiamare cancella su end ()?
martedì

16

Si noti che m_CursorStack.erase( (++i).base())potrebbe essere un problema se utilizzato in un forciclo (vedere la domanda originale) perché modifica il valore di i. L'espressione corretta èm_CursorStack.erase((i+1).base())


4
Dovresti creare una copia dell'iteratore e farlo iterator j = i ; ++j, perché i+1non funziona su un iteratore, ma questa è l'idea giusta
bobobobo

3
@bobobobo, è possibile utilizzare m_CursorStack.erase(boost::next(i).base())con Boost. o in C ++ 11m_CursorStack.erase(std::next(i).base())
alfC

12

... o un altro modo per rimuovere questo elemento dall'elenco?

Ciò richiede il -std=c++11flag (per auto):

auto it=vt.end();
while (it>vt.begin())
{
    it--;
    if (*it == pCursor) //{ delete *it;
        it = vt.erase(it); //}
}

Funziona un incanto :)
Den-Jason

@GaetanoMendola: perché?
slashmais,

3
Chi ti garantisce che gli iteratori in un elenco siano ordinati?
Gaetano Mendola,

7

Divertente che non ci sia ancora una soluzione corretta su questa pagina. Quindi, il seguente è quello corretto:

Nel caso dell'iteratore in avanti la soluzione è semplice:

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

In caso di iteratore inverso devi fare lo stesso:

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;
  } 
}

Appunti:

  • Puoi costruire un reverse_iteratorda un iteratore
  • È possibile utilizzare il valore restituito di std::list::erase

Questo codice funziona, ma spiega perché viene utilizzato next e come è sicuro trasmettere un iteratore in avanti a un iteratore invertito senza che il mondo collassi
Lefteris E

1
@LefterisE Non è un cast. Crea un nuovo iteratore inverso da un interatore. Questo è normale costruttore dell'iteratore inverso.
Šimon Tóth,

3

Durante l'utilizzo del reverse_iterator's base()metodo e decrementare il risultato lavora qui, vale la pena notare che reverse_iterators non è dato lo stesso status regolari iterators. In generale, si dovrebbe preferire regolari iterators a reverse_iterators (così come per const_iterators ed const_reverse_iterators), per motivi esattamente come questo. Vedi il Journal del dottor Dobbs per una discussione approfondita del perché.


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--;
}

3

Ed ecco il pezzo di codice per convertire il risultato della cancellazione in un iteratore inverso al fine di cancellare un elemento in un contenitore durante l'iterazione al contrario. Un po 'strano, ma funziona anche quando si cancella il primo o l'ultimo 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;
}

2

Se non hai bisogno di cancellare tutto mentre procedi, per risolvere il problema, puoi usare il linguaggio cancella-rimuovi:

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

std::removescambia tutti gli elementi nel contenitore che corrispondono pCursoralla fine e restituisce un iteratore al primo elemento di corrispondenza. Quindi, l' eraseutilizzo di un intervallo cancellerà dalla prima corrispondenza e andrà alla fine. L'ordine degli elementi non corrispondenti viene conservato.

Questo potrebbe funzionare più velocemente se stai usando a std::vector , dove la cancellazione nel mezzo dei contenuti può comportare molte copie o spostamenti.

O ovviamente, le risposte sopra che spiegano l'uso di reverse_iterator::base()sono interessanti e vale la pena sapere, per risolvere il problema esatto dichiarato, direi che std::removeè una soluzione migliore.


1

Volevo solo chiarire qualcosa: in alcuni dei commenti e risposte sopra riportati la versione portatile per la cancellazione è menzionata come (++ i) .base (). Tuttavia, a meno che non manchi qualcosa, l'istruzione corretta è (++ ri) .base (), il che significa che 'incrementa' il reverse_iterator (non l'iteratore).

Ieri ho avuto la necessità di fare qualcosa di simile e questo post è stato utile. Grazie a tutti.


0

Per integrare le risposte degli altri e poiché mi sono imbattuto in questa domanda durante la ricerca di std :: string senza molto successo, ecco una risposta con l'uso di std :: string, std :: string :: erase e std :: reverse_iterator

Il mio problema era cancellare il nome di un file immagine da una stringa di nome file completa. Inizialmente è stato risolto con std :: string :: find_last_of, ma io cerco un modo alternativo con 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\'

Questo utilizza algoritmo, iteratore e intestazioni di stringa.


0

l'iteratore inverso è piuttosto difficile da usare. Quindi ho usato l'iteratore generale. 'r' Inizia dall'ultimo elemento. Quando trovi qualcosa da cancellare. cancellalo e ritorna al prossimo iteratore. ad es. quando si elimina il 3o elemento, indicherà il 4o elemento corrente. e nuovo terzo. Quindi dovrebbe essere ridotto di 1 per spostarsi a sinistra

void remchar(string& s,char c)
{      
    auto r = s.end() - 1;
    while (r >= s.begin() && *r == c)
    {
        r = s.erase(r);
        r -= 1;
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.