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?
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;
}
}
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é.
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)));
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.
- std::remove_if. ou list:: remove_if (si vous savez que vous travaillez avec list et non avec la TCollection)
- std:: for_each
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);
}
}
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);
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é.
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;
}));
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.
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);
}