So rufen Sie Erase mit einem Reverse-Iterator auf

181

Ich versuche so etwas zu tun:

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

Das Löschen erfordert jedoch einen Iterator und keinen umgekehrten Iterator. Gibt es eine Möglichkeit, einen umgekehrten Iterator in einen regulären Iterator umzuwandeln, oder eine andere Möglichkeit, dieses Element aus der Liste zu entfernen?

0xC0DEFACE
quelle
17
Abgesehen davon, berechnen Sie beim Schreiben von Schleifen wie diesen nicht wiederholt den Enditerator, wie Sie es hier tun i != m_CursorStack.rend(). Schreiben Sie stattdessen i = m_CursorStack.rbegin(), end = m_CursorStack.rend(); i != end;. Initialisieren Sie also einen Iterator, den Sie für einen wiederholten Vergleich behalten können - vorausgesetzt, die Endposition ändert sich nicht als Nebeneffekt Ihres Schleifenkörpers.
seh
Es scheint mir, dass die offensichtliche Frage hier wäre, warum Sie das überhaupt tun. Was bringt es Ihnen, die Liste in umgekehrter Reihenfolge zu durchlaufen? Was gewinnen Sie, wenn Sie diesen Code selbst schreiben, anstatt ihn zu verwenden std::remove?
Jerry Coffin
Und ist ein Iterator in einer std :: -Liste noch gültig, um zu erhöhen, nachdem das Element, auf das er verweist, gelöscht wurde?
Steve Jessop
3
Ich möchte nur 1 Element entfernen, daher würde das 'break;' mit 'remove' alle Übereinstimmungen beseitigen, die länger dauern und nicht das tun, was ich will. Das Element, das ich in diesem speziellen Fall entfernen möchte, ist fast immer das Ende der Liste oder sehr nahe daran, sodass die umgekehrte Iteration auch schneller und besser für das Problem geeignet ist.
0xC0DEFACE
3
stackoverflow.com/a/2160581/12386 Dies besagt, dass die Designer die Implementierung nicht speziell definieren, da Sie als Benutzer nichts davon wissen oder sich darum kümmern sollen. @seh oben erwartet jedoch, dass wir auf magische Weise nur wissen, dass rend () berechnet wird und teuer.
stu

Antworten:

181

Nach einigen weiteren Recherchen und Tests fand ich die Lösung. Anscheinend ist gemäß dem Standard [24.4.1 / 1] die Beziehung zwischen i.base () und i:

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

(aus einem Artikel von Dr. Dobbs ):

Alt-Text

Sie müssen also einen Offset anwenden, wenn Sie die base () erhalten. Daher lautet die Lösung:

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

BEARBEITEN

Aktualisierung für C ++ 11.

reverse_iterator ibleibt unverändert:

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

reverse_iterator iist erweitert:

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

Ich finde das viel klarer als meine vorherige Lösung. Verwenden Sie, was Sie benötigen.

0xC0DEFACE
quelle
27
Sie sollten etwas mehr von dem Artikel beachten, den Sie zitiert haben - um portabel zu sein, sollte der Ausdruck sein m_CursorStack.erase( (++i).base())(Mann, wenn ich dieses Zeug mit Reverse-Iteratoren mache, tut mir der Kopf weh ...). Es sollte auch beachtet werden, dass der DDJ-Artikel in Meyers "Effective STL" -Buch aufgenommen wurde.
Michael Burr
8
Ich finde dieses Diagramm eher verwirrend als hilfreich. Seit rbegin zeigen ri und rend tatsächlich auf das Element rechts von dem, auf das sie zeigen sollen. Das Diagramm zeigt, auf welches Element Sie zugreifen würden, wenn Sie *sie verwenden. Wir sprechen jedoch darüber, auf welches Element Sie zeigen würden, wenn Sie basesie verwenden. Dies ist ein Element rechts. Ich bin kein großer Fan von --(i.base())oder (++i).base()Lösungen, da sie den Iterator mutieren. Ich bevorzuge, (i+1).base()was auch funktioniert.
mgiuca
4
Reverse-Iteratoren sind Lügner. Wenn sie zurückgestellt werden, gibt ein Reverse-Iterator das Element davor zurück . Siehe hier
Bobobobo
4
Um ganz klar zu sein, kann diese Technik immer noch nicht in einer normalen for-Schleife verwendet werden (wobei der Iterator auf normale Weise inkrementiert wird). Siehe stackoverflow.com/questions/37005449/…
logidelic
1
m_CursorStack.erase ((++ i) .base ()) scheint ein Problem zu sein, wenn ++ Sie dazu bringen würde, das letzte Element zu überwinden. kannst du erase on end () aufrufen?
stu
16

Bitte beachten Sie, dass m_CursorStack.erase( (++i).base())dies ein Problem sein kann, wenn es in einer forSchleife verwendet wird (siehe ursprüngliche Frage), da es den Wert von i ändert. Richtiger Ausdruck istm_CursorStack.erase((i+1).base())

Andrey
quelle
4
Sie müssten eine Kopie des Iterators erstellen und tun iterator j = i ; ++j, da i+1dies nicht auf einem Iterator funktioniert, aber das ist die richtige Idee
Bobobobo
3
@ Bobobobo, können Sie m_CursorStack.erase(boost::next(i).base())mit Boost verwenden. oder in C ++ 11m_CursorStack.erase(std::next(i).base())
alfC
12

... oder eine andere Möglichkeit, dieses Element aus der Liste zu entfernen?

Dies erfordert das -std=c++11Flag (für auto):

auto it=vt.end();
while (it>vt.begin())
{
    it--;
    if (*it == pCursor) //{ delete *it;
        it = vt.erase(it); //}
}
Slashmais
quelle
Funktioniert ein Zauber :)
Den-Jason
@ GaetanoMendola: warum?
Slashmais
3
Wer garantiert Ihnen, dass Iteratoren in einer Liste bestellt werden?
Gaetano Mendola
7

Komisch, dass es auf dieser Seite noch keine richtige Lösung gibt. Folgendes ist also richtig:

Im Fall des Vorwärtsiterators ist die Lösung einfach:

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

Im Falle eines Reverse-Iterators müssen Sie dasselbe tun:

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

Anmerkungen:

  • Sie können einen reverse_iteratoraus einem Iterator erstellen
  • Sie können den Rückgabewert von verwenden std::list::erase
Gaetano Mendola
quelle
Dieser Code funktioniert, aber bitte erklären Sie, warum next verwendet wird und wie es sicher ist, einen Vorwärtsiterator in einen Rückwärtsiterator umzuwandeln, ohne dass die Welt zusammenbricht
Lefteris E
1
@LefterisE Das ist keine Besetzung. Es wird ein neuer Reverse-Iterator aus einem Interator erstellt. Dies ist der normale Konstruktor des Reverse-Iterators.
Šimon Tóth
3

Während die Verwendung der Methode reverse_iterator's base()und das Dekrementieren des Ergebnisses hier funktioniert, ist zu beachten, dass reverse_iterators nicht den gleichen Status wie reguläre iterators erhalten. Im Allgemeinen sollten Sie aus genau diesen Gründen reguläres iterators gegenüber reverse_iterators (sowie gegenüber const_iterators und const_reverse_iterators) bevorzugen . Im Doctor Dobbs 'Journal finden Sie eine ausführliche Diskussion darüber, warum.

Adam Rosenfield
quelle
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
quelle
3

Und hier ist der Code, mit dem das Ergebnis des Löschens wieder in einen umgekehrten Iterator konvertiert werden kann, um ein Element in einem Container zu löschen, während in umgekehrter Reihenfolge iteriert wird. Ein bisschen seltsam, aber es funktioniert auch beim Löschen des ersten oder letzten Elements:

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
quelle
2

Wenn Sie im Laufe der Zeit nicht alles löschen müssen, können Sie zur Lösung des Problems die Redewendung "Löschen-Entfernen" verwenden:

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

std::removetauscht alle Elemente im Container aus, die pCursormit dem Ende übereinstimmen , und gibt einen Iterator an das erste Übereinstimmungselement zurück. Dann wird die eraseVerwendung eines Bereichs aus dem ersten Spiel gelöscht und geht bis zum Ende. Die Reihenfolge der nicht übereinstimmenden Elemente bleibt erhalten.

Dies kann für Sie schneller funktionieren, wenn Sie eine verwenden std::vector, bei der das Löschen in der Mitte des Inhalts viel Kopieren oder Verschieben erfordern kann.

Oder natürlich sind die obigen Antworten, die die Verwendung von erklären, reverse_iterator::base()interessant und wissenswert. Um das genaue Problem zu lösen, würde ich argumentieren, dass dies std::removebesser passt.

gavinbeatty
quelle
1

Ich wollte nur etwas klarstellen: In einigen der obigen Kommentare und Antworten wird die tragbare Version zum Löschen als (++ i) .base () erwähnt. Wenn mir jedoch etwas fehlt, lautet die korrekte Anweisung (++ ri) .base (), was bedeutet, dass Sie den reverse_iterator (nicht den Iterator) 'inkrementieren'.

Ich hatte gestern das Bedürfnis, etwas Ähnliches zu tun, und dieser Beitrag war hilfreich. Vielen Dank an alle.

user1493570
quelle
0

Um die Antworten anderer zu ergänzen und weil ich bei der Suche nach std :: string ohne großen Erfolg auf diese Frage gestoßen bin, folgt eine Antwort mit der Verwendung von std :: string, std :: string :: erase und std :: reverse_iterator

Mein Problem war das Löschen des Bilddateinamens aus einer vollständigen Dateinamenzeichenfolge. Es wurde ursprünglich mit std :: string :: find_last_of gelöst, aber ich recherchiere einen alternativen Weg mit 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\'

Dies verwendet Algorithmus-, Iterator- und String-Header.

fmmarques
quelle
0

Reverse Iterator ist ziemlich schwer zu bedienen. Also nur allgemeinen Iterator verwendet. 'r' Es beginnt mit dem letzten Element. Wenn Sie etwas zum Löschen finden. Löschen Sie es und geben Sie den nächsten Iterator zurück. Wenn Sie beispielsweise das 3. Element löschen, wird das aktuelle 4. Element angezeigt. und neuer 3 .. Es sollte also um 1 verringert werden, um nach links zu gehen

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
quelle