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 souhaite supprimer les éléments inactifs immédiatement après leur mise à jour, afin d'éviter de repasser dans la liste. Mais si j'ajoute les lignes commentées, j'obtiens une erreur lorsque j'arrive à i++
: "Liste des itérateurs non incrémentable". J'ai essayé des remplaçants qui n'ont pas augmenté dans la déclaration for, mais je ne pouvais rien faire fonctionner.
Quel est le meilleur moyen de supprimer des éléments lorsque vous parcourez une liste std :: list?
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 renvoyée par i ++). Vous pouvez changer le code en une boucle while comme ceci:
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);
Cela mettra correctement à jour l'itérateur pour qu'il pointe vers l'emplacement après l'itérateur que vous avez supprimé.
Vous devez combiner les réponses de Kristo et celles de MSN:
// 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 != 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, le plus efficace et le plus efficace des produits SuperCool® STL serait le suivant:
// 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)));
Utilisez l'algorithme std :: remove_if.
Edit: Le travail avec les collections devrait ressembler à: 1. préparer la collection. 2. collecte de processus.
La vie sera plus facile si vous ne mélangez pas ces étapes.
Voici un exemple d'utilisation d'une boucle for
qui itère la liste et incrémente ou revalide l'itérateur dans le cas où un élément serait supprimé lors du parcours 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);
L'alternative pour la version en boucle à la réponse de Kristo.
Vous perdez un peu d'efficacité, vous effectuez un retour en arrière puis un renvoi lors de la suppression, mais en échange de l'incrémentation supplémentaire de l'itérateur, vous pouvez faire déclarer l'itérateur dans l'étendue de la boucle et donner au code un aspect moins net. Que choisir dépend des priorités du moment.
La réponse était totalement hors du 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);
}
}
Je l'ai résumé, voici les trois méthodes avec exemple:
while
list<int> lst{4, 1, 2, 3, 5};
auto it = lst.begin();
while (it != lst.end()){
if((*it % 2) == 1){
it = lst.erase(it);// erase and go to next
} else{
++it; // go to next
}
}
for(auto it:lst)cout<<it<<" ";
cout<<endl; //4 2
remove_if
fonction membre dans la liste:list<int> lst{4, 1, 2, 3, 5};
lst.remove_if([](int a){return a % 2 == 1;});
for(auto it:lst)cout<<it<<" ";
cout<<endl; //4 2
std::remove_if
en combinaison avec la fonction membre erase
:list<int> lst{4, 1, 2, 3, 5};
lst.erase(std::remove_if(lst.begin(), lst.end(), [](int a){
return a % 2 == 1;
}), lst.end());
for(auto it:lst)cout<<it<<" ";
cout<<endl; //4 2
for
, notez la mise à jour de l'itérateur:list<int> lst{4, 1, 2, 3, 5};
for(auto it = lst.begin(); it != lst.end();++it){
if ((*it % 2) == 1){
it = lst.erase(it); erase and go to next(erase will return the next iterator)
--it; // as it will be add again in for, so we go back one step
}
}
for(auto it:lst)cout<<it<<" ";
cout<<endl; //4 2
Tu peux é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 un code équivalent avec std::list::remove_if
, qui est moins détaillé et plus explicite.
items.remove_if([] (item*i) {
bool isActive = (*i)->update();
if (!isActive)
return true;
other_code_involving(*i);
return false;
});
Le langage std::vector::erase
std::remove_if
doit être utilisé lorsque items est un vecteur et non une liste afin de conserver la compexité sur O(n) - ou si vous écrivez du code générique et des éléments. un conteneur sans moyen efficace d'effacer des éléments isolés (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;
}));
La suppression invalide uniquement les itérateurs qui pointent sur les éléments supprimés.
Ainsi, dans ce cas, après avoir supprimé * i, i est invalidé et vous ne pouvez pas y incrémenter.
Ce que vous pouvez faire est d’abord enregistrer l’itérateur de l’élément à supprimer, puis incrémenter l’itérateur, puis supprimer celui qui a été enregistré.
Si vous pensez que le std::list
ressemble à une file d'attente, vous pouvez alors mettre en file d'attente tous les éléments que vous souhaitez conserver et les mettre en file d'attente, mais uniquement (et non en file d'attente) l'élément à supprimer. Voici un exemple où je veux supprimer 5 d'une liste contenant les numéros 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 chiffres 1-4 et 6-10.
Une itération en arrière permet d'éviter d'effacer un élément sur les éléments restants à parcourir:
typedef list<item*> list_t;
for ( list_t::iterator it = items.end() ; it != items.begin() ; ) {
--it;
bool remove = <determine whether to remove>
if ( remove ) {
items.erase( it );
}
}
PS: voir this , par exemple en ce qui concerne l'itération en arrière.
PS2: Je n’ai pas fait de tests approfondis s’il gère bien les éléments d’effacement aux extrémités.