OVH Cloud OVH Cloud

map : suppression via un itérateur et incrémentation de l'itérateur

11 réponses
Avatar
MGN
bonjour,
une question bête, mais je veux être sûr de ne pas me tromper :-)

_values est un objet map

code :

it=std::find_if(_values.begin(),_values.end(),upper_keys);
while(it!=_values.end())
{
_values.erase(it);
it=std::find_if(it,_values.end(),upper_keys);
// <- ma question est ici
// it=std::find_if(++it,_values.end(),upper_keys);
<- version fausse

}

je supprime l'objet pointé donc il ne faut pas incrémenter
l'itérateur...C'est bien ça ?
Il n'y a pas de risque avec cette écriture ? par exemple si it pointait vers
le dernier élément du conteneur ?
Merci pour vos lumières
Marc

10 réponses

1 2
Avatar
kanze
MGN wrote:

une question bête, mais je veux être sûr de ne pas me tromper :-)

_values est un objet map


C-à-d de type std::map<X,Y>, je suppose.

code :
it=std::find_if(_values.begin(),_values.end(),upper_k eys);
while(it!=_values.end())
{
_values.erase(it);
it=std::find_if(it,_values.end(),upper_keys);
// <- ma question est ici
// it=std::find_if(++it,_values.end(),upper_keys);
<- version fausse


Les deux sont faux. Après l'erase, it n'est plus valid. Tu ne
peux rien faire avec lui. Strictement parlant, même pas le
copier, mais en aucun cas le passer comme paramètre à un
algorithme ou à une fonction membre. Il y a deux possibilités :

_values.erase( it ++ ) ;
it = std::find_if( it, _values.end(), upper_keys ) ;

On incrémente la copie locale avant de faire l'erase. Ou (celui
que je préfère) :

it = std::find_if( _values.erase( it ), _values.end(), upper_keys )
;

tout simplement. Les fonctions erase renvoie normalement
l'itérateur de l'élément qui suivait l'élément qu'ils ont
supprimé.

}

je supprime l'objet pointé donc il ne faut pas incrémenter
l'itérateur...C'est bien ça ?


Ni l'incrémenter, ni le passer comme paramètre à une fonction.

Il n'y a pas de risque avec cette écriture ? par exemple si it
pointait vers le dernier élément du conteneur ?


Après l'incrémentation, ou la valeur de rétour de erase(), peut
bien désigner la fin du map. Mais ça ne doit pas poser de
problème : on a droit de passer une suite vide à un algorithme
de la bibliothèque standard. (L'implémentation typique de
find_if serait quelque chose du genre :

while ( begin != end && ! predicate( *begin ) ) {
++ begin ;
}
return begin ;

Si les deux itérateurs sont égaux, on n'entre pas dans la
boucle.)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
MGN
je te remercie BEAUCOUP !
Le pire, c'est que j'ai fait un logiciel qui marche très bien depuis 4 ans
(utilisé quotidiennement par environ 100 personnes...) et dans lequel je
fait des erase(it) sur des vector !
Bon, je vais rechercher partout mes erase dans mes programmes pour corriger
!
Merci encore
Marc
Avatar
MGN
Malheur, dans ma version de la STL (j'utilise BC6), erase ne retourne pas
d'iterateur.
Extrait de la doc :

void erase(iterator position);
Deletes the map element pointed to by the iterator position. Returns an
iterator pointing to the element following the deleted element, or end() if
the deleted item was the last one in this list.

Le moins qu'on puisse dire, c'est qu'il y a contradiction entre le texte et
le prototype de la fonction !
Mais si j'essaye, j'ai bien un message d'erreur !

Je me pose une question. Quand tu écris

_values.erase( it ++ ) ;
it = std::find_if( it, _values.end(), upper_keys ) ;

ça marche parce qu'il n'y a pas de réallocation de mémoire pour les éléments
du conteneur map après un erase, alors que dans un vecteur, tous les
éléments du vecteurs sont décalés après un erase (d'où l'inefficacité d'un
suppression en milieu de vecteur).

En conclusion, pour un vecteur, après
vecteur.erase(it);
it pointe vers l'élément suivant immédiatement l'élément supprimé, sans
qu'il faille l'incrémenter (par it++ par exemple)
C'est ça ?
Marc
Avatar
kanze
MGN wrote:

Le pire, c'est que j'ai fait un logiciel qui marche très bien
depuis 4 ans (utilisé quotidiennement par environ 100
personnes...) et dans lequel je fait des erase(it) sur des
vector !


Formellement, c'est un comportement indéfini, mais dans la
pratique, l'itérateur désignera l'élément suivant, s'il existe,
et sinon end(). Pour std::vector ; non pour les autres
collections.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
kanze
MGN wrote:
Malheur, dans ma version de la STL (j'utilise BC6), erase ne
retourne pas d'iterateur.

Extrait de la doc :

void erase(iterator position);

Deletes the map element pointed to by the iterator position.
Returns an iterator pointing to the element following the
deleted element, or end() if the deleted item was the last one
in this list.


Intéressant. Signature « void... », puis « Returns... »
dans la description. Il faudrait essayer, pour voir lequel est
juste. (Et j'espère pour toi que la qualité de l'implémentation
est mieux que la qualité de la documentation.)

Le moins qu'on puisse dire, c'est qu'il y a contradiction
entre le texte et le prototype de la fonction !

Mais si j'essaye, j'ai bien un message d'erreur !


Ce que je conseille, c'est un autre compilateur, ou au moins une
autre version de la bibliothèque. VC++ est maintenant disponible
gratuitement, et g++ l'est depuis toujours. À mon avis, un
téléchargement de Visual Studios 2005 ou de MinWG s'impose.

Je me pose une question. Quand tu écris

_values.erase( it ++ ) ;
it = std::find_if( it, _values.end(), upper_keys ) ;

ça marche parce qu'il n'y a pas de réallocation de mémoire
pour les éléments du conteneur map après un erase, alors que
dans un vecteur, tous les éléments du vecteurs sont décalés
après un erase (d'où l'inefficacité d'un suppression en milieu
de vecteur).


Ça marche parce que la norme dit qu'erase n'invalide que les
itérateurs qui désignent un objet effacé pour liste, mais
qu'elle invalide tous les itérateurs sur des éléments qui suit
sur un vecteur. (Mais la raison pour cette différence, c'est
effectivement ce que tu dis.)

En conclusion, pour un vecteur, après
vecteur.erase(it);
it pointe vers l'élément suivant immédiatement l'élément
supprimé, sans qu'il faille l'incrémenter (par it++ par
exemple)

C'est ça ?


Non. Pour un vector, tout itérateur vers l'élément supprimé OU
vers un élément qui lui suivait dans la suite devient invalid.
Ce que tu décris est vrai dans des implémentations typiques
simplistes, mais ce n'est pas garanti, et des versions recentes
de g++ (et de VC++ aussi, je crois, mais je ne l'ai pas essayé)
font en sorte qu'il y a un échec d'une assertion si tu essaies
de t'en servir.

La façon prévue par la norme de résoudre ce problème, c'est
d'utiliser la valeur de rétour de erase() ; c'est pour ça
qu'elle est là. (Elle manquait dans les collections associatives
dans la première version de la norme, mais ça a été corrigé
depuis.) Sinon, il faut une solution différente par type de
collection : avec vector et deque, grosso modo, il faut prendre
l'indice (iter - begin()), puis réconstituer l'itérateur après
la suppression (begin() + indice) ; si tu sais que ce n'est pas
le premier élément, tu pourrais aussi garder une copie de
itérateur moins 1, et incrémenter ça par la suite. Pour list, la
solution que j'ai indiqué marche. Pour les collections
associatives (et il y a bien plus de chances pour que erase
renvoie void dans ces cas-là, parce que la renvoie de
l'itérateur est une correction plus récente), la seule solution
qui me vient à l'esprit, c'est de sauvegarde la clé avant, puis
utiliser upper_bound pour se répositionner après la suppression.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
MGN
merci
c'est pénible ces différentes versions de la STL.
Et bien sûr, pas question d'y toucher...
Avatar
MGN
j'ai pas bien compris la dernière partie de ta réponse sur les collections
associatives...
Quand j'écris pour un conteneur map
_values.erase(it++);
it=std::find_if(it,_values.end(),upper_keys);
ça ne marche pas ?
Nota : j'ai regardé le fichier map.h de mon implémentation de la STL et j'ai
bien
void erase(iterator position);
Hélas...
Marc
Avatar
kanze
MGN wrote:
j'ai pas bien compris la dernière partie de ta réponse sur les collec tions
associatives...
Quand j'écris pour un conteneur map
_values.erase(it++);
it=std::find_if(it,_values.end(),upper_keys);
ça ne marche pas ?


Tout à fait. Je pensais en fait à la fonction erase qui prend
des clés ; elle peut bien en effacer plus d'un élément (pas
dans map, mais en général).

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
Fabien LE LEZ
On Thu, 12 Oct 2006 09:28:06 +0200, "MGN" :

c'est pénible ces différentes versions de la STL.


Différents bugs dans les implémentations, tu veux dire ?

Et bien sûr, pas question d'y toucher...


On peut changer l'implémentation de la STL, soit en changeant tout le
système (par exemple en utilisant VC++ 2005 à la place d'un vieux
compilo), soit en changeant juste la STL (par exemple, en mettant
celle de SGI à la place de celle fournie avec le compilo).

Avatar
Marc G
compilo), soit en changeant juste la STL (par exemple, en mettant
celle de SGI à la place de celle fournie avec le compilo).
j'ai l'impression que ça se fait pas comme ça avec mon compilo (C++ Builder

6). J'ai des constantes symboliques définies un peu partout et propres à mon
implémentation. Et puis si des classes de la VCL utilisent (ce qui me semble
très probable) la STL. Si je change d'implémentation, je risque la
catastrophe. Non ?
Marc

1 2