116 votes

Supprimer un élément du vecteur alors que vous êtes dans la plage c ++11 pour une boucle?

J'ai un vecteur de IInventory *. Je suis en train de parcourir la liste en utilisant c ++ 11 range pour, faire des choses avec chacun. Après avoir fait quelques choses avec un, je peux vouloir le retirer de la liste et supprimer l'objet. Je sais que je peux appeler delete sur le pointeur à tout moment pour le nettoyer, mais quelle est la bonne façon de le supprimer du vecteur, alors qu'il se trouve dans la plage de bouclage? Et si je le retire de la liste, ma boucle sera-t-elle invalidée?

 std::vector<IInventory*> inv;
inv.push_back( new Foo() );
inv.push_back( new Bar() );

for ( IInventory* index : inv )
    {
    // do some stuff
    // ok I decided I need to remove this object from inv...?
    }
 

108voto

Seth Carnegie Points 45196

Non, tu ne peux pas. for basé sur une plage est destiné au moment où vous devez accéder à chaque élément d'un conteneur une fois.

Vous devez utiliser la boucle normale for ou l’un de ses cousins si vous devez modifier le conteneur au fur et à mesure, accéder à un élément plus d’une fois ou, sinon, parcourir le conteneur de manière non linéaire.

Par exemple:

 auto i = std::begin(inv);

while (i != std::end(inv)) {
    // do some stuff
    if (blah)
        i = inv.erase(i);
    else
        ++i;
}
 

58voto

Branko Dimitrijevic Points 28493

Chaque fois qu'un élément est supprimé du vecteur, vous devez supposer que les itérateurs situés à ou après l'élément supprimé ne sont plus valides, car chacun des éléments succédant à l'élément supprimé est déplacé.

Une boucle for basée sur une plage est simplement un sucre syntaxique pour une boucle "normale" utilisant des itérateurs, de sorte que ce qui précède s'applique.

Cela étant dit, vous pouvez simplement:

 inv.erase(
    std::remove_if(
        inv.begin(),
        inv.end(),
        [](IInventory* element) -> bool {
            // Do "some stuff", then return true if element should be removed.
            return true;
        }
    ),
    inv.end()
);
 

17voto

dirkgently Points 56879

Idéalement, vous ne devriez pas modifier le vecteur lors de l'itération sur elle. Utiliser le erase-remove idiome. Si vous le faites, vous êtes susceptible de rencontrer quelques problèmes. Depuis en vector un erase invalide tous les itérateurs de début avec l'élément à effacer jusqu'à l' end() vous devez vous assurer que votre itérateurs restent valables à l'aide de:

for (MyVector::iterator b = v.begin(); b != v.end();) { 
    if (foo) {
       b = v.erase( b ); // reseat iterator to a valid value post-erase
    else {
       ++b;
    }
}

Notez que vous avez besoin de l' b != v.end() test-est. Si vous essayer de l'optimiser comme suit:

for (MyVector::iterator b = v.begin(), e = v.end(); b != e;)

vous allez courir dans UB depuis votre e est invalidée après la première erase appel.

4voto

Yexo Points 806

Est-ce une obligation stricte de supprimer des éléments dans cette boucle? Sinon, vous pouvez définir les pointeurs que vous souhaitez supprimer sur NULL et effectuer un autre passage sur le vecteur pour supprimer tous les pointeurs NULL.

 std::vector<IInventory*> inv;
inv.push_back( new Foo() );
inv.push_back( new Bar() );

for ( IInventory* &index : inv )
{
    // do some stuff
    // ok I decided I need to remove this object from inv...?
    if (do_delete_index)
    {
        delete index;
        index = NULL;
    }
}
std::remove(inv.begin(), inv.end(), NULL);
 

0voto

bobobobo Points 17477

Beaucoup plus élégant solution serait de passer à std::list (en supposant que vous n'avez pas besoin d'accès aléatoire rapide).

list<Widget*> widgets ; // create and use this..

Vous pouvez ensuite les supprimer avec .remove_if et un C++ foncteur en une seule ligne:

widgets.remove_if( []( Widget*w ){ return w->isExpired() ; } ) ;

Donc ici, je suis en train d'écrire un foncteur qui accepte un argument ( Widget*). La valeur de retour est la condition de supprimer un Widget* à partir de la liste.

Je trouve cette syntaxe acceptable. Je ne pense pas que j'aurais jamais les utiliser, remove_if pour les std::vecteurs -- il y a tellement d' inv.begin() et inv.end() le bruit de là, vous êtes probablement mieux d'utiliser un entier fondé sur l'indice de supprimer ou juste un simple vieux régulières itérateur à base de supprimer (comme illustré ci-dessous). Mais vous ne devrait pas vraiment être enlever du milieu d'un std::vector beaucoup de toute façon, le fait de passer à un list pour ce cas de fréquentes moyen de la liste de suppression est conseillé.

Notez cependant je n'ai pas de possibilité d'appeler d' delete sur le Widget*'s qui ont été supprimés. Pour ce faire, il devrait ressembler à ceci:

widgets.remove_if( []( Widget*w ){
  bool exp = w->isExpired() ;
  if( exp )  delete w ;       // delete the widget if it was expired
  return exp ;                // remove from widgets list if it was expired
} ) ;

Vous pouvez également utiliser un itérateur à base de boucle comme ceci:

//                                                              NO INCREMENT v
for( list<Widget*>::iterator iter = widgets.begin() ; iter != widgets.end() ; )
{
  if( (*iter)->isExpired() )
  {
    delete( *iter ) ;
    iter = widgets.erase( iter ) ; // _advances_ iter, so this loop is not infinite
  }
  else
    ++iter ;
}

Si vous n'aimez pas la longueur de l' for( list<Widget*>::iterator iter = widgets.begin() ; ..., vous pouvez utiliser

for( auto iter = widgets.begin() ; ...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X