OVH Cloud OVH Cloud

'erase' et validité de l'itérateur

5 réponses
Avatar
Vincent Richard
Bonjour,

Le code suivant est-il valide ?

std::vector <Objet*> v;

// ...

for (std::vector<Objet*>::iterator it = v.begin() ; it != v.end() ; ++it)
{
// On supprime tous les éléments '123' du vecteur
if ((*it)->valeur == 123)
{
Objet* const obj = *it;
v.erase(it);

// Appel d'une fonction pour notifier la suppression de l'élément
f(obj);
delete obj;

// PROBLEME : 'it' est-il toujours valide ici ??? Peut-on
// continuer à itérer ?
}
}

Et si non, comment faire ?

Merci d'avance pour vos réponses.

Vincent

--
SL> Au fait elle est mieux ma signature maintenant ?
Oui. T'enlève encore les conneries que t'as écrit dedans et c'est bon.
-+- JB in <http://www.le-gnu.net> : Le neuneuttoyage par le vide -+-

5 réponses

Avatar
Fabien LE LEZ
On Sun, 17 Aug 2003 00:57:48 +0200, Vincent Richard
wrote:

for (std::vector<Objet*>::iterator it = v.begin() ; it != v.end() ; ++it)
{
// On supprime tous les éléments '123' du vecteur
if ((*it)->valeur == 123)
{
Objet* const obj = *it;


J'aime pas le "const" ici. En effet, tu déclares ici un pointeur
constant vers un "Objet". Or l'appel à "delete" va transformer ce
pointeur en un pointeur invalide. Certes, le "const" est valide du
point de vue C++, mais il ne me paraît pas logique.

v.erase(it);

// Appel d'une fonction pour notifier la suppression de l'élément
f(obj);
delete obj;


Perso, je mettrais "delete" avant "erase". Mais bon, ce n'est que mon
feeling ;-)


// PROBLEME : 'it' est-il toujours valide ici ?


Non (du moins, ce n'est pas garanti).

Une solution est d'utiliser la valeur de retour de erase(), qui est
sensée être un itérateur sur l'élément suivant :

for (std::vector<Objet*>::iterator it = v.begin(); it != v.end();)
{
// On supprime tous les éléments '123' du vecteur
if ((*it)->valeur == 123)
{
Objet* const obj = *it;
..
it= v.erase(it);
}
else
{
++it;
}
}

Deux bémols toutefois :
- il est des implémentations de la STL pour lesquelles erase()
ne renvoie pas de valeur (celle de BC++ 5.02). Si tu n'utilises que
des compilateurs récents, ça ne devrait pas poser de problèmes, mais
garde ça à l'esprit en cas d'erreur de compilation lors d'un portage.
- std::vector<> est adapté pour des ajouts/suppressions à la
fin, et l'accès aléatoire. Il n'est pas du tout adapté à
l'insertion/suppression d'éléments au milieu : non seulement c'est
lent, mais en plus ça invalide tous les itérateurs. Si tu as beaucoup
de suppressions à faire (et pas d'accès aléatoire), je conseille
plutôt std::list<> :

for (std::list<Objet*>::iterator it = v.begin(); it != v.end();)
{
// On supprime tous les éléments '123' du vecteur
if ((*it)->valeur == 123)
{
Objet* const obj = *it;
..
v.erase(it++);
}
else
{
++it;
}
}


--
Tout sur fr.* (FAQ, etc.) : http://www.usenet-fr.net/fur/
et http://www.aminautes.org/forums/serveurs/tablefr.html
Archives : http://groups.google.com/advanced_group_search
http://www.usenet-fr.net/fur/usenet/repondre-sur-usenet.html

Avatar
Fabien LE LEZ
On Sun, 17 Aug 2003 09:25:49 +0200, Vincent Richard
wrote:

v.erase(it++);


Je suppose qu'ici, il faut lire :

it = v.erase(it);


Non : ce que j'avais écrit fonctionne avec std::list<> ; de plus, il
me semble que std::list<>::erase ne renvoie pas forcément un itérateur
sur l'élément suivant.


--
Tout sur fr.* (FAQ, etc.) : http://www.usenet-fr.net/fur/
et http://www.aminautes.org/forums/serveurs/tablefr.html
Archives : http://groups.google.com/advanced_group_search
http://www.usenet-fr.net/fur/usenet/repondre-sur-usenet.html


Avatar
Patrick Mézard
v.erase(it);

// Appel d'une fonction pour notifier la suppression de
l'élément


f(obj);
delete obj;


Perso, je mettrais "delete" avant "erase". Mais bon, ce n'est que mon
feeling ;-)


Même si dans son exemple l'ordre n'a pas d'importance (à condition que le
destructeur de Objet ne lance pas d'exception), tu préfères conserver
(transitoirement) un élément invalide (car détruit) dans le conteneur avant
de le retirer, plutôt que de retirer un élément valide avant de le détruire
(quitte à risquer la fuite de mémoire) ?

Patrick Mézard


Avatar
Patrick Mézard
Soit T un type quelconque

void g (T&);

vector<T> v= ...;
vector<T>::iterator it= ...;

A priori, je préfère l'écriture

g (*it);
v.erase (it);

à l'écriture

T temp (*it);
v.erase (it);
g (temp);

(quitte à risquer la fuite de mémoire) ?


Garder un pointeur sur lequel on a appelé delete peut éventuellement
générer un core dump et/ou un comportement indéfini si on le
déréférence, mais pas une fuite mémoire, puisque delete a été appelé.


Oui justement, en supposant que g() soit modifiante ET susceptible d'échouer
en laissant T dans un état non-valide (et potentiellement en balançant une
exception ou que sais-je), je préfère copier "it", le virer du conteneur,
puis le modifier, pour ne pas me retrouver avec un élement dans un état
indéterminé dans le conteneur.

Dans le cas soulevé par Vincent, tout cela est bien entendu surperflu car il
manipule des pointeurs (donc constructeur de copie et assignation en
throw(), donc erase() en throw()) et son destructeur ne lance bien sûr pas
d'exceptions donc on est tranquilles.

Patrick Mézard


Avatar
Fabien LE LEZ
On Sun, 17 Aug 2003 14:52:13 +0200, "Patrick Mézard"
wrote:

Oui justement, en supposant que g() soit modifiante ET susceptible d'échouer
en laissant T dans un état non-valide (et potentiellement en balançant une
exception ou que sais-je), je préfère copier "it", le virer du conteneur,
puis le modifier, pour ne pas me retrouver avec un élement dans un état
indéterminé dans le conteneur.


Mais une fonction g() vraiment exception-safe doit :
- soit nettoyer correctement l'objet et retourner normalement ;
- soit laisser l'objet sans modification et lancer une
exception.

Exemple :

void g (Objet* &o)
{
if (o.OnPeutSupprimer() == false)
{
throw "Supression impossible";
}
/* Eventuellement ici, un code qui ne peut pas lancer d'exception */
delete o;
}

Si tu as un objet dans un état indéfini, le code ne peut pas avoir un
comportement défini de toutes façons :
- soit tu l'enlèves du conteneur, avec un risque de fuite
mémoire ;
- soit tu le laisses dans le conteneur, avec un risque
d'utiliser un objet invalide.



--
Tout sur fr.* (FAQ, etc.) : http://www.usenet-fr.net/fur/
et http://www.aminautes.org/forums/serveurs/tablefr.html
Archives : http://groups.google.com/advanced_group_search
http://www.usenet-fr.net/fur/usenet/repondre-sur-usenet.html