Le §23.1.2.8 de la norme stipule que les opérations d'insertion/suppression sur un ensemble/map n'invalideront pas les itérateurs vers ces objets (sauf les itérateurs pointant vers un élément supprimé).
Considérons maintenant la situation suivante : vous voulez mettre en œuvre un graphe avec des nœuds à numérotation unique, où chaque nœud a un nombre fixe (disons 4) de voisins. En tirant parti de la règle ci-dessus, vous procédez comme suit :
class Node {
private:
// iterators to neighboring nodes
std::map<int, Node>::iterator neighbors[4];
friend class Graph;
};
class Graph {
private:
std::map<int, Node> nodes;
};
( EDIT : Pas littéralement comme cela en raison de l'incomplétude des Node
à la ligne 4 (voir réponses/commentaires), mais dans ce sens quand même)
C'est une bonne chose, car de cette façon, vous pouvez insérer et supprimer des nœuds sans invalider la cohérence de la structure (en supposant que vous gardez la trace des suppressions et que vous supprimez l'itérateur supprimé du tableau de chaque nœud).
Mais supposons que vous souhaitiez également être en mesure de stocker une valeur de voisin "invalide" ou "inexistante". Pas d'inquiétude, nous pouvons simplement utiliser nodes.end()
... ou le pouvons-nous ? Y a-t-il une sorte de garantie que nodes.end()
à 8 heures du matin sera le même que nodes.end()
à 22 heures après un million d'insertions/d'effacements ? C'est-à-dire, est-ce que je peux en toute sécurité ==
compare un itérateur reçu en tant que paramètre de la fonction nodes.end()
dans une méthode de Graph ?
Et si non, est-ce que ça marcherait ?
class Graph {
private:
std::map<int, Node> nodes;
std::map<int, Node>::iterator _INVALID;
public:
Graph() { _INVALID = nodes.end(); }
};
C'est-à-dire, est-ce que je peux stocker nodes.end()
dans une variable lors de la construction, et ensuite utiliser cette variable chaque fois que je veux mettre un voisin dans un état invalide, ou le comparer à un paramètre dans une méthode ? Ou est-il possible qu'à un moment donné, un itérateur valide pointant vers un objet existant compare égal à _INVALID
?
Et si ça ne marche pas non plus, que peut Je fais en sorte de laisser de la place pour une valeur voisine invalide ?
3 votes
C'est une très bonne question.
4 votes
En fait, j'ai juste fait la chose la plus évidente et vérifié 23.1.2/8 dans la norme : "Les membres d'insertion ne doivent pas affecter la validité des itérateurs et des références au conteneur, et les membres d'effacement ne doivent invalider que les itérateurs et les références aux éléments effacés." Ceci est sans ambiguïté : la seule interprétation possible est que les itérateurs end() vont pas être invalidée. Merci à Kirill V. Lyadvinsky de m'avoir incité à vérifier !
3 votes
La norme indique explicitement que
end()
restera toujours valide, mais d'après ce que je peux dire, tout ce que cela signifie, c'est que le fait d'appelerend()
n'échouera pas. Le résultat d'une comparaison entre deuxend()
interrogé à des moments différents est, du moins pour moi, une question entièrement différente.1 votes
@suszterpatt : Je pense que le terme "itérateur reste valide" pour un itérateur de fin n'a de sens que s'il compare égal avec l'itérateur de fin actuel. Voir ma réponse : stackoverflow.com/questions/1432216/1432613#1432613
0 votes
@sbi : cela a certainement du sens, mais bien que j'apprécie le bon sens, je cherche une réponse plus tangible ici. Je suppose que la question se résume vraiment à la définition de la "validité" dans le cas de
end()
.0 votes
@j_random_hacker : Pavel interprète la même phrase différemment de vous et moi. Et bien que je ne pense pas que son interprétation ait du sens, je ne considère certainement pas que la phrase soit sans ambiguïté.
0 votes
@Kirill : La question n'est pas de savoir si, dans le cas où cela est aussi clair que
1+1
il sera vrai même après un million d'itérations. La question est de savoir si cela est aussi clair que1+1
.3 votes
Dire qu'elle reste valable signifie que vous pouvez sauvegarder une valeur obtenue à 7 heures du matin et l'utiliser encore à 7 heures du soir. Si c'était le cas, cela signifierait que sa valeur n'est pas égale à la valeur réelle.
end
alors il serait devenu un itérateur singulier, et la norme parle de valeurs d'itérateurs singuliers : "les résultats de la plupart des expressions sont indéfinis pour les valeurs singulières", article 24.1/5.0 votes
@suszterpatt : J'ai essayé de répondre au message de Kirill mais il semble qu'il ait été supprimé... "Valide" signifie que tous les comportements requis attendus d'un itérateur (par exemple, comme indiqué dans le tableau 74 pour les itérateurs avant) s'appliquent. Plus précisément, cela inclut les exigences suivantes : (I) toute façon de copier ou d'assigner un itérateur a à b satisfait à a == b comme postcondition, et (II) a == b est une relation d'équivalence, ce qui signifie que cette relation d'égalité est transitive (c'est-à-dire que si a == b et b == c alors a == c). Dans l'ensemble, cela signifie que, oui, end() ne sera pas modifié par les insertions/suppressions ! :-)
1 votes
Je pense que c'est clair : end() retourne un itérateur vers l'élément que l'on a passé à end. L'insertion/suppression n'affecte pas les itérateurs existants donc les valeurs retournées sont toujours valides (sauf si vous essayez de supprimer l'élément one passed then end (mais cela résulterait en un comportement non défini de toute façon). Ainsi, tout nouvel itérateur généré par end (serait différent) mais, lorsqu'il est comparé à l'original à l'aide de operator==, il renvoie vrai. De même, toute valeur intermédiaire générée à l'aide de l'opérateur d'affectation= est soumise à une condition a posteriori, à savoir qu'elle doit être comparée à l'opérateur== et que celui-ci est transitif pour les itérateurs.