Pouvez-vous Supprimer des éléments d'une liste std::tout en itérant à travers elle?

j'ai un code qui ressemble à ceci:

for (std::list<item*>::iterator i=items.begin();i!=items.end();i++)
{
    bool isActive = (*i)->update();
    //if (!isActive) 
    //  items.remove(*i); 
    //else
       other_code_involving(*i);
}
items.remove_if(CheckItemNotActive);

je voudrais supprimer les éléments inactifs immédiatement après leur mise à jour, afin d'éviter de parcourir à nouveau la liste. Mais si j'ajoute commenté les lignes, j'ai une erreur quand j'arrive à i++ : "Liste itérateur pas incrementable". J'ai essayé quelques alternats qui n'ont pas augmenté dans la déclaration, mais je n'ai rien pu obtenir à travailler.

Quelle est la meilleure façon de supprimer des éléments que vous la marche d'un std::list?

209
demandé sur AShelly 2009-02-27 22:08:20

10 réponses

vous devez d'abord incrémenter l'itérateur (avec i++) puis supprimer l'élément précédent (par exemple, en utilisant la valeur retournée de i++). Vous pouvez changer le code en boucle while comme suit:

std::list<item*>::iterator i = items.begin();
while (i != items.end())
{
    bool isActive = (*i)->update();
    if (!isActive)
    {
        items.erase(i++);  // alternatively, i = items.erase(i);
    }
    else
    {
        other_code_involving(*i);
        ++i;
    }
}
245
répondu Michael Kristofik 2009-02-27 21:15:23

Vous voulez faire:

i= items.erase(i);

qui mettra à jour correctement l'itérateur pour pointer vers l'emplacement après l'itérateur que vous avez supprimé.

108
répondu MSN 2009-02-27 19:18:02

Vous devez faire la combinaison de Kristo la réponse de MSN est:

// Note: Using the pre-increment operator is preferred for iterators because
//       there can be a performance gain.
//
// Note: As long as you are iterating from beginning to end, without inserting
//       along the way you can safely save end once; otherwise get it at the
//       top of each loop.

std::list< item * >::iterator iter = items.begin();
std::list< item * >::iterator end  = items.end();

while (iter != items.end())
{
    item * pItem = *iter;

    if (pItem->update() == true)
    {
        other_code_involving(pItem);
        ++iter;
    }
    else
    {
        // BTW, who is deleting pItem, a.k.a. (*iter)?
        iter = items.erase(iter);
    }
}

bien sûr, la chose la plus efficace et SuperCool® STL savy serait quelque chose comme ceci:

// This implementation of update executes other_code_involving(Item *) if
// this instance needs updating.
//
// This method returns true if this still needs future updates.
//
bool Item::update(void)
{
    if (m_needsUpdates == true)
    {
        m_needsUpdates = other_code_involving(this);
    }

    return (m_needsUpdates);
}

// This call does everything the previous loop did!!! (Including the fact
// that it isn't deleting the items that are erased!)
items.remove_if(std::not1(std::mem_fun(&Item::update)));
19
répondu Mike 2009-02-27 21:12:11

utiliser l'algorithme std::remove_if.

Edit: Travailler avec des collections devrait être comme: 1. préparer la collecte. 2. processus de collecte.

la vie sera plus facile si vous ne mélangez pas ces étapes.

  1. std::remove_if. ou list:: remove_if (si vous savez que vous travaillez avec list et non avec la TCollection)
  2. std:: for_each
10
répondu Mykola Golubyev 2009-03-01 13:42:34

l'alternative pour La boucle version de Kristo réponse.

vous perdez un peu d'efficacité, vous revenez en arrière puis en avant lors de la suppression, mais en échange de l'incrément itérateur supplémentaire vous pouvez avoir l'itérateur déclaré dans la portée de boucle et le code à l'air un peu plus propre. Le choix dépend des priorités du moment.

la réponse était totalement hors de temps, je sais...

typedef std::list<item*>::iterator item_iterator;

for(item_iterator i = items.begin(); i != items.end(); ++i)
{
    bool isActive = (*i)->update();

    if (!isActive)
    {
        items.erase(i--); 
    }
    else
    {
        other_code_involving(*i);
    }
}
4
répondu Rafael Gago 2011-08-05 12:57:59

Voici un exemple d'utilisation d'un for boucle qui itère la liste et incrémente ou revalide l'itérateur dans le cas d'un élément supprimé lors de la traversée de la liste.

for(auto i = items.begin(); i != items.end();)
{
    if(bool isActive = (*i)->update())
    {
        other_code_involving(*i);
        ++i;

    }
    else
    {
        i = items.erase(i);

    }

}

items.remove_if(CheckItemNotActive);
4
répondu David Cormack 2013-07-04 20:37:39
La suppression de

n'invalide que les itérateurs qui pointent vers les éléments supprimés.

donc dans ce cas , après avoir enlevé *i, je suis invalidé et vous ne pouvez pas faire d'incrément sur elle.

Ce que vous pouvez faire est d'abord enregistrer l'itérateur de l'élément à supprimer , puis incrémenter la variable d'itération, puis retirez le rescapé.

2
répondu anand 2009-02-27 19:29:58

Vous pouvez écrire

std::list<item*>::iterator i = items.begin();
while (i != items.end())
{
    bool isActive = (*i)->update();
    if (!isActive) {
        i = items.erase(i); 
    } else {
        other_code_involving(*i);
        i++;
    }
}

vous pouvez écrire le code équivalent avec std::list::remove_if , qui est moins verbeux et plus explicite

items.remove_if([] (item*i) {
    bool isActive = (*i)->update();
    if (!isActive) 
        return true;

    other_code_involving(*i);
    return false;
});

l'idiome std::vector::erase std::remove_if doit être utilisé lorsque les éléments sont un vecteur au lieu d'une liste pour garder la compexité à O (n) - ou dans le cas où vous écrivez le code générique et les éléments peuvent être un conteneur sans moyen efficace pour effacer des éléments simples (comme un vecteur)

items.erase(std::remove_if(begin(items), end(items), [] (item*i) {
    bool isActive = (*i)->update();
    if (!isActive) 
        return true;

    other_code_involving(*i);
    return false;
}));
2
répondu J. Kalz 2016-03-26 09:06:31

si vous pensez au std::list comme une file d'attente, alors vous pouvez déqueue et se renseigner sur tous les articles que vous voulez garder, mais seulement dequeue (et non se renseigner) l'article que vous voulez enlever. Voici un exemple où je veux supprimer 5 d'une liste contenant les nombres 1-10...

std::list<int> myList;

int size = myList.size(); // The size needs to be saved to iterate through the whole thing

for (int i = 0; i < size; ++i)
{
    int val = myList.back()
    myList.pop_back() // dequeue
    if (val != 5)
    {
         myList.push_front(val) // enqueue if not 5
    }
}

myList n'aura plus que les numéros 1-4 et 6-10.

2
répondu Alex Bagg 2016-12-18 11:22:18

je pense que vous avez un bug là, je code de cette façon:

for (std::list<CAudioChannel *>::iterator itAudioChannel = audioChannels.begin();
             itAudioChannel != audioChannels.end(); )
{
    CAudioChannel *audioChannel = *itAudioChannel;
    std::list<CAudioChannel *>::iterator itCurrentAudioChannel = itAudioChannel;
    itAudioChannel++;

    if (audioChannel->destroyMe)
    {
        audioChannels.erase(itCurrentAudioChannel);
        delete audioChannel;
        continue;
    }
    audioChannel->Mix(outBuffer, numSamples);
}
-4
répondu Marcin Skoczylas 2012-09-18 16:15:53