Comment appeler effacer avec un itérateur inversé

191

J'essaye de faire quelque chose comme ça:

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

Cependant, l'effacement prend un itérateur et non un itérateur inverse. existe-t-il un moyen de convertir un itérateur inverse en itérateur régulier ou un autre moyen de supprimer cet élément de la liste?

0xC0DEFACE
la source
17
En passant, lorsque vous écrivez des boucles comme celles-ci, ne calculez pas à plusieurs reprises l'itérateur de fin comme vous le faites ici avec i != m_CursorStack.rend(). Au lieu de cela, écrivez i = m_CursorStack.rbegin(), end = m_CursorStack.rend(); i != end;. Autrement dit, initialisez un itérateur que vous pouvez conserver pour des comparaisons répétées - en supposant que la position finale ne changera pas en tant qu'effet secondaire de votre corps de boucle.
seh
Il me semble que la question évidente ici serait de savoir pourquoi vous faites cela. Que gagnez-vous à parcourir la liste à l'envers? Que gagnez-vous en écrivant ce code vous-même au lieu de l'utiliser std::remove?
Jerry Coffin
Et un itérateur sur un std :: list est-il toujours valide pour incrémenter après que l'élément auquel il se réfère a été effacé?
Steve Jessop
3
Je veux seulement supprimer 1 élément, donc le «break;», utiliser «remove» éliminerait tout ce qui prend plus de temps et ne fait pas ce que je veux. L'élément que je veux supprimer dans ce cas particulier sera presque toujours la fin de la liste ou très proche de celle-ci, donc l'itération en sens inverse est également plus rapide et mieux adaptée au problème.
0xC0DEFACE
4
stackoverflow.com/a/2160581/12386 Cela dit que les concepteurs ne définissent pas spécifiquement l'implémentation parce que vous, en tant qu'utilisateur, n'êtes pas censé savoir ou s'en soucier, mais @seh ci-dessus s'attend à ce que nous sachions par magie que rend () est calculé et cher.
STU

Réponses:

187

Après quelques recherches et tests supplémentaires, j'ai trouvé la solution. Apparemment, selon la norme [24.4.1 / 1], la relation entre i.base () et i est:

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

(extrait d'un article du Dr Dobbs ):

texte alternatif

Vous devez donc appliquer un décalage lors de l'obtention de la base (). Par conséquent, la solution est:

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

ÉDITER

Mise à jour pour C ++ 11.

reverse_iterator iest inchangé:

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

reverse_iterator iest avancé:

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

Je trouve cela beaucoup plus clair que ma solution précédente. Utilisez celui dont vous avez besoin.

0xC0DEFACE
la source
27
Vous devriez prendre note un peu plus de l'article que vous avez cité - pour être portable, l'expression devrait être m_CursorStack.erase( (++i).base())(mec, faire ce truc avec des itérateurs inversés me fait mal à la tête ...). Il convient également de noter que l'article DDJ est incorporé dans le livre "Effective STL" de Meyer.
Michael Burr
8
Je trouve ce diagramme plus déroutant qu'utile. Depuis rbegin, ri et Pourfendre sont en fait montrant l'élément à droite de ce qu'ils sont attirés pointer à. Le diagramme montre à quel élément vous accéderiez si vous les *utilisiez, mais nous parlons de l'élément que vous pointeriez si vous les baseutilisiez, qui est un élément à droite. Je ne suis pas fan des solutions --(i.base())ou (++i).base()car elles font muter l'itérateur. Je préfère (i+1).base()ce qui fonctionne aussi.
mgiuca
4
Les itérateurs inversés sont des menteurs .. lorsqu'ils sont déférés, un itérateur inversé renvoie l'élément qui le précède . Voir ici
bobobobo
4
Pour être tout à fait clair, cette technique ne peut toujours pas être utilisée dans une boucle for normale (où l'itérateur est incrémenté de la manière normale). Voir stackoverflow.com/questions/37005449/…
logidelic
1
m_CursorStack.erase ((++ i) .base ()) semble que ce serait un problème si ++ je vous amenais à passer le dernier élément. pouvez-vous appeler effacer à la fin ()?
STU
16

Veuillez noter que cela m_CursorStack.erase( (++i).base())peut poser un problème s'il est utilisé dans une forboucle (voir la question d'origine) car cela change la valeur de i. L'expression correcte estm_CursorStack.erase((i+1).base())

Andrey
la source
4
Vous auriez besoin de créer une copie de l'itérateur et de le faire iterator j = i ; ++j, car i+1ne fonctionne pas sur un itérateur, mais c'est la bonne idée
bobobobo
3
@bobobobo, vous pouvez utiliser m_CursorStack.erase(boost::next(i).base())avec Boost. ou en C ++ 11m_CursorStack.erase(std::next(i).base())
alfC
12

... ou une autre façon de supprimer cet élément de la liste?

Cela nécessite le -std=c++11drapeau (pour auto):

auto it=vt.end();
while (it>vt.begin())
{
    it--;
    if (*it == pCursor) //{ delete *it;
        it = vt.erase(it); //}
}
slashmais
la source
Works a charme :)
Den-Jason
@GaetanoMendola: pourquoi?
slashmais
3
Qui vous garantit que les itérateurs d'une liste sont ordonnés?
Gaetano Mendola
7

C'est drôle qu'il n'y ait pas encore de solution correcte sur cette page. Donc, ce qui suit est le bon:

Dans le cas de l'itérateur avant, la solution est simple:

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

En cas d'itérateur inversé, vous devez faire de même:

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

Remarques:

  • Vous pouvez construire un à reverse_iteratorpartir d'un itérateur
  • Vous pouvez utiliser la valeur de retour de std::list::erase
Gaetano Mendola
la source
Ce code fonctionne, mais veuillez expliquer pourquoi next est utilisé et comment est-il sûr de convertir un itérateur avant en itérateur inverse sans que le monde ne s'effondre
Lefteris E
1
@LefterisE Ce n'est pas un casting. Il crée un nouvel itérateur inverse à partir d'un interateur. Il s'agit du constructeur normal de l'itérateur inverse.
Šimon Tóth le
3

Bien que l'utilisation de la méthode reverse_iterators base()et la décrémentation du résultat fonctionnent ici, il convient de noter que les reverse_iterators n'ont pas le même statut que les iterators réguliers . En général, vous devriez préférer les iterators réguliers à reverse_iterators (ainsi qu'aux const_iterators et const_reverse_iterators), pour des raisons précises comme celle-ci. Voir le Journal du Docteur Dobbs pour une discussion approfondie sur les raisons.

Adam Rosenfield
la source
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
la source
3

Et voici le morceau de code pour reconvertir le résultat de l'effacement en un itérateur inverse afin d'effacer un élément dans un conteneur tout en itérant à l'inverse. Un peu étrange, mais cela fonctionne même en effaçant le premier ou le dernier élément:

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;
}
étham
la source
2

Si vous n'avez pas besoin de tout effacer au fur et à mesure, pour résoudre le problème, vous pouvez utiliser l'idiome effacer-supprimer:

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

std::removeéchange tous les éléments du conteneur qui correspondent pCursorà la fin et renvoie un itérateur vers le premier élément correspondant. Ensuite, l' eraseutilisation d'une plage effacera de la première correspondance et ira jusqu'à la fin. L'ordre des éléments non correspondants est conservé.

Cela pourrait fonctionner plus rapidement pour vous si vous utilisez un std::vector , où l'effacement au milieu du contenu peut impliquer beaucoup de copie ou de déplacement.

Ou bien sûr, les réponses ci-dessus expliquant l'utilisation de reverse_iterator::base()sont intéressantes et valent la peine d'être connues, pour résoudre le problème exact posé, je dirais que std::removec'est un meilleur ajustement.

gavinbeatty
la source
1

Je voulais juste clarifier quelque chose: dans certains des commentaires et réponses ci-dessus, la version portable pour l'effacement est mentionnée comme (++ i) .base (). Cependant, à moins que je ne manque quelque chose, la déclaration correcte est (++ ri) .base (), ce qui signifie que vous «incrémentez» le reverse_iterator (pas l'itérateur).

J'ai eu besoin de faire quelque chose de similaire hier et cet article m'a été utile. Merci tout le monde.

user1493570
la source
0

Pour compléter les réponses des autres et parce que je suis tombé sur cette question en cherchant sur std :: string sans grand succès, voici une réponse avec l'utilisation de std :: string, std :: string :: erase et std :: reverse_iterator

Mon problème effaçait le nom de fichier d'une image d'une chaîne de nom de fichier complète. Il a été résolu à l'origine avec std :: string :: find_last_of, mais je recherche une autre manière avec 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\'

Cela utilise des en-têtes d'algorithme, d'itérateur et de chaîne.

fmmarques
la source
0

L'itérateur inversé est assez difficile à utiliser. Donc, juste utilisé itérateur général. 'r' Il commence à partir du dernier élément. Quand trouver quelque chose à effacer. effacez-le et retournez l'itérateur suivant. Par exemple, lors de la suppression du 3e élément, il pointera le 4e élément actuel. et nouveau 3e. Il devrait donc être diminué de 1 pour se déplacer à gauche

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
la source