Comment effacer et supprimer des pointeurs vers des objets stockés dans un vecteur?
J'ai un vecteur qui stocke des pointeurs vers de nombreux objets instanciés dynamiquement, et j'essaie d'itérer à travers le vecteur et de supprimer certains éléments (supprimer du vecteur et détruire l'objet), mais j'ai des problèmes. Voici à quoi il ressemble:
vector<Entity*> Entities;
/* Fill vector here */
vector<Entity*>::iterator it;
for(it=Entities.begin(); it!=Entities.end(); it++)
if((*it)->getXPos() > 1.5f)
Entities.erase(it);
Lorsque l'un des objets Entity atteint xPos > 1.5, le programme se bloque avec une erreur d'assertion... Quelqu'un sait ce que je fais mal?
J'utilise VC++ 2008.
5 réponses
Vous devez être prudent, car erase()
invalidera existant itérateurs. Cependant, ir renvoie un nouvel itérateur valide que vous pouvez utiliser:
for ( it = Entities.begin(); it != Entities.end(); )
if( (*it)->getXPos() > 1.5f )
delete * it;
it = Entities.erase(it);
}
else {
++it;
}
}
La "bonne" façon de le faire est d'utiliser un algorithme:
#include <algorithm>
#include <functional>
// this is a function object to delete a pointer matching our criteria.
struct entity_deleter
{
void operator()(Entity*& e) // important to take pointer by reference!
{
if (e->GetXPos() > 1.5f)
{
delete e;
e = NULL;
}
}
// now, apply entity_deleter to each element, remove the elements that were deleted,
// and erase them from the vector
for_each(Entities.begin(), Entities.end(), entity_deleter());
vector<Entity*>::iterator new_end = remove(Entities.begin(), Entities.end(), static_cast<Entity*>(NULL));
Entities.erase(new_end, Entities.end());
Maintenant, je sais ce que vous pensez. Vous pensez que certaines des autres réponses sont plus courtes. Mais, (1) cette méthode compile généralement en code plus rapide-essayez de la comparer, (2) c'est la méthode STL "correcte", (3) Il y a moins de chance pour des erreurs stupides, et (4) il est plus facile à lire une fois que vous pouvez lire le code STL. Cela vaut la peine d'apprendre la programmation STL, et je vous suggère de vérifier le Grand Livre de Scott Meyer "Effective STL" qui a des tas de conseils STL sur ce genre de choses.
Un autre point important est qu'en n'effaçant pas les éléments jusqu'à la fin de l'opération, les éléments n'ont pas besoin d'être mélangés. GMan suggérait d'utiliser une liste pour éviter cela, mais en utilisant cette méthode, l'opération entière est O (n). Le code de Neil ci-dessus, en revanche, est O(N^2), puisque la recherche est O(n) et la suppression est O(n).
if((*it)->getXPos() > 1.5f)
{
delete *it;
it = Entities.erase(it);
}
Une fois que vous modifiez le vecteur, tous les itérateurs en suspens deviennent invalides. En d'autres termes, vous ne pouvez pas modifier le vecteur pendant que vous l'itérez. Pensez à ce que cela fait à la mémoire et vous verrez pourquoi. Je soupçonne que votre affirmation est une affirmation" itérateur invalide".
Std::vector::erase() retourne un itérateur que vous devez utiliser pour remplacer celui que vous utilisez. Voir ici.
Le problème principal est que la plupart des itérateurs de conteneurs stl ne prennent pas en charge l'ajout ou la suppression d'éléments dans le conteneur. Certains invalideront tous les itérateurs, d'autres n'invalideront qu'un itérateur pointant vers un élément supprimé. Jusqu'à ce que vous ayez une meilleure idée du fonctionnement de chacun des conteneurs, vous devrez faire attention à lire la documentation sur ce que vous pouvez et ne pouvez pas faire à un conteneur.
Les conteneurs Stl n'appliquent pas une implémentation particulière, mais un vecteur est généralement mis en œuvre par un tableau sous le capot. Si vous supprimez un élément au début, tous les autres éléments sont déplacés. Si vous aviez un itérateur pointant vers l'un des autres éléments, il pourrait maintenant être en pointant sur l'élément après l'ancien élément. Si vous ajoutez un élément, le tableau peut avoir besoin d'être redimensionnées, de sorte qu'un nouveau tableau est fait, les vieux trucs copié, et maintenant votre itérateur pointant vers l'ancienne version du vecteur, ce qui est mauvais.
Pour votre problème, il devrait être sûr de parcourir le vecteur en arrière et supprimez les éléments au fur et à mesure. Il sera également légèrement plus rapide, puisque vous ne serez pas en mouvement autour des éléments que vous allez supprimer.
vector<Entity*> Entities;
/* Fill vector here */
vector<Entity*>::iterator it;
for(it=Entities.end(); it!=Entities.begin(); ){
--it;
if(*(*it) > 1.5f){
delete *it;
it=Entities.erase(it);
}
}