OVH Cloud OVH Cloud

iterator = NULL

58 réponses
Avatar
Henri de Solages
Bonjour.

Est-il légal d'assigner la valeur NULL à un itérateur ?

--
Get rid of the final underscore of my address to use it.

8 réponses

2 3 4 5 6
Avatar
Gabriel Dos Reis
Jean-Marc Bourguet writes:

| Marc Boyer writes:
|
| > Gabriel Dos Reis a écrit :
| > > Marc Boyer writes:
| > >| Ils font une liste circulaire ?
| > >
| > > En quelque sorte. Mais un noeud dans cette liste « circulaire » sert à
| > > marquer end(), donc de l'extérieur ce n'est pas une liste circulaire.
| > >
| > > Mais on peut expliquer pourquoi le code ci-dessus va boucler
| > > indéfiniment (à condition que l.size() > 0) sans regarder une
| > > implémentation particulière.
| >
| > A condition de comprendre que end() est modifié par
| > le push_back(), c'est à dire que end() dépend du contenu
| > de la liste.
| >
| > Et puis il y a cette connaissance que les itérateurs de
| > liste ne sont pas invalidés par les insertions. En
| > réflechissant 2mn, on réalise que begin() est fondamentalement
| > invalidé par un push_front(), donc, on peut se méfier du
| > fait que end() le soit de temps en temps.
| >
| > Mais en fait, faudrait que je prenne un papier et un crayon
| > peut-être, mais je n'arrive pas à voir ce qui invalide end()
| > dans l'implémentation de liste quasi-circulaire dont nous parlons.
|
| end() n'est pas invalide. En fait ca boucle ou non suivant
| l'implementation de copy.
|
| Si copy est du genre
|
| while (begin != end) {
| *dest = *begin;
| ++begin;
| }
|
| On boucle. Si copy est du genre
| while (begin != end) {
| _It cur(begin);
| ++begin;
| *dest = *cur;
| }
| on ne boucle pas. Y a-t'il

Mais je crois que cela n'est pas une implémentation générique valide de
std::copy(). La raison est que std::copy attend un InputIterator : pour un
tel iterateur, si tu fais une copie, tu n'as plus d'assurance que
l'itérateur copié est encore bon pour faire quoi que ce soit (en
particulier tester pour égalité). Voir la table 72, en particulier la phrase

pre: r is dereferenceable.
post: r is dereferenceable or r is past-the-end.
post: any copies of the previous value of r are no longer
required either to be dereferenceable or to be in the domain
of ==.

Maintenant, si tu écris plusieurs versions de std::copy() suivant la
catégorie d'itérateurs, alors... Mains même là je ne serais pas
d'accord avec toi, parce que _It peut être essentiellement un pointeur
vers un noeud et dans ce cas, que tu copies l'itérateur ou non, ne
change rien à l'affaire.

-- Gaby
Avatar
kanze
Marc Boyer wrote:
Marc Boyer writes:
| Ils font une liste circulaire ?

En quelque sorte. Mais un noeud dans cette liste «
circulaire » sert à marquer end(), donc de l'extérieur ce
n'est pas une liste circulaire.

Mais on peut expliquer pourquoi le code ci-dessus va boucler
indéfiniment (à condition que l.size() > 0) sans regarder
une implémentation particulière.


A condition de comprendre que end() est modifié par le
push_back(), c'est à dire que end() dépend du contenu de la
liste.


Dans le cas d'une liste, je ne crois pas.

Une fonction comme push_back() ne modifie jamais d'itérateur.
(C'est évident, si tu y penses. Il ne les connaît pas.) Alors,
si tu as un itérateur dans une collection, et que tu y fais
push_back(), la « valeur » de ton itérateur ne change pas, et
des deux choses une : l'itérateur a été invalidé, ou l'itérateur
est encore valide, et désigne le même objet (ou désigne toujours
un derrière le dernier objet, si c'était un itérateur de fin).

Si push_back() invalide l'itérateur ou non dépend de la
collection. Selon la norme, push_back() vaut insert( end() ).
Tu insères donc après tout élément (mais avant end()). Selon la
norme, l'insertion dans un std::list n'invalide aucun itérateur.
Donc, quelque chose comme :

std::list< int >::iterator i = l.end() ;
l.push_back( x ) ;
assert( i == l.end() ) ;

est garantie.

Et puis il y a cette connaissance que les itérateurs de liste
ne sont pas invalidés par les insertions. En réflechissant
2mn, on réalise que begin() est fondamentalement invalidé par
un push_front(), donc, on peut se méfier du fait que end() le
soit de temps en temps.


D'abord, begin() et end() ne sont jamais invalidés. Je crois que
ce que tu voulais dire, c'est qu'un itérateur qui contient le
résultat de begin() serait invalidé. Mais c'est faux aussi;
l'itérateur reste valide, et désigne toujours l'élément qu'il
désignait avant. (C'est d'ailleurs la définition de validité
d'une itérateur -- qu'il désigne l'élément qu'il a désigné
avant.) Seulement (évidemment ?), cet élément n'est plus le
premier dans la collection, et donc, ton itérateur n'est plus
égal à begin().

Dans le cas d'end(), conceptuellement, on pourrait dire qu'il
désigne un élément virtuel qui est toujours derrière le dernier
élément réel. Donc, avec std::list, si tu initialises un
itérateur avec end(), cet itérateur sera toujours valide (à
condition que la liste elle-même n'est pas détruite), et sera
toujours égal à end().

Note qu'en revanche, une insertion dans un std::vector ou dans
un std::deque invalide toujours un itérateur qui valait end()
avant.

Mais en fait, faudrait que je prenne un papier et un crayon
peut-être, mais je n'arrive pas à voir ce qui invalide end()
dans l'implémentation de liste quasi-circulaire dont nous
parlons.


On parle ici d'une classe standard. Donc, formellement, on n'a
pas à régarder l'implémentation, mais les garanties de la norme.
Pratiquement, évidemment, tu as raison, et si la norme garantit
la validité d'un itérateur qui contient end() dans ce cas-ci,
c'est bien parce que l'implémentation « canonique » ne
l'invalide pas. Comme tu as pu constaté.

--
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
Jean-Marc Bourguet
Gabriel Dos Reis writes:

| On boucle. Si copy est du genre
| while (begin != end) {
| _It cur(begin);
| ++begin;
| *dest = *cur;
| }
| on ne boucle pas. Y a-t'il

Mais je crois que cela n'est pas une implémentation générique valide de
std::copy().


Merci. Tu reponds a la question coupee :-)

A+
--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
kanze
Gabriel Dos Reis wrote:
Jean-Marc Bourguet writes:

| Marc Boyer writes:

| > > Marc Boyer writes:
| > >| Ils font une liste circulaire ?

| > > En quelque sorte. Mais un noeud dans cette liste «
| > > circulaire » sert à marquer end(), donc de l'extérieur
| > > ce n'est pas une liste circulaire.

| > > Mais on peut expliquer pourquoi le code ci-dessus va
| > > boucler indéfiniment (à condition que l.size() > 0) sans
| > > regarder une implémentation particulière.

| > A condition de comprendre que end() est modifié par le
| > push_back(), c'est à dire que end() dépend du contenu de
| > la liste.

| > Et puis il y a cette connaissance que les itérateurs de
| > liste ne sont pas invalidés par les insertions. En
| > réflechissant 2mn, on réalise que begin() est
| > fondamentalement invalidé par un push_front(), donc, on
| > peut se méfier du fait que end() le soit de temps en
| > temps.

| > Mais en fait, faudrait que je prenne un papier et un
| > crayon peut-être, mais je n'arrive pas à voir ce qui
| > invalide end() dans l'implémentation de liste
| > quasi-circulaire dont nous parlons.

| end() n'est pas invalide. En fait ca boucle ou non suivant
| l'implementation de copy.

| Si copy est du genre

| while (begin != end) {
| *dest = *begin;
| ++begin;
| }

| On boucle. Si copy est du genre
| while (begin != end) {
| _It cur(begin);
| ++begin;
| *dest = *cur;
| }
| on ne boucle pas.


Je présume que c'est « *dest ++ = ... » que tu voulais dans les
deux cas. Bien que ça ne change rien ici, où dest est un
back_insertion_iterator.

Mais je crois que cela n'est pas une implémentation générique
valide de std::copy(). La raison est que std::copy attend un
InputIterator : pour un tel iterateur, si tu fais une copie,
tu n'as plus d'assurance que l'itérateur copié est encore bon
pour faire quoi que ce soit (en particulier tester pour
égalité). Voir la table 72, en particulier la phrase

pre: r is dereferenceable.
post: r is dereferenceable or r is past-the-end.
post: any copies of the previous value of r are no longer
required either to be dereferenceable or to be in the domain
of ==.

Maintenant, si tu écris plusieurs versions de std::copy()
suivant la catégorie d'itérateurs, alors... Mains même là je
ne serais pas d'accord avec toi, parce que _It peut être
essentiellement un pointeur vers un noeud et dans ce cas, que
tu copies l'itérateur ou non, ne change rien à l'affaire.


On parle d'un cas très précis : begin et end sont des
std::list<>::iterator (ou const_iterator), et dest est un
back_insertion_iterator. Le point de Jean-Marc, c'est que
l'sous-expression « *dest = » peut modifier le pointeur next du
noeud. Alors, si (comme on l'imaginera), ++begin fait en fait
quelque chose comme « begin = begin->next » (et faisant
abstraction des diverses couches intermédiaire), qu'on le fasse
avant *dest = ou après peut bien changer le comportement.

Encore plus amusant c'est ce qui est probablement
l'implémentation réele : « *dest ++ = *begin ++ ». Où c'est
indéfini ce qui se passe.

Dans la spécification de std::copy, il y a une contrainte :
« result shall not be in the range [first, last). » Je ne sais
pas comment appliquer cette phrase au juste si result est un
back_insertion_iterator, mais je crois qu'on pourrait bien
arguer que l'intention est que cet appel soit invalide.

Plus généralement, je ne sais pas appliquer cette phrase si les
itérateurs sont de types différents. Mais je crois que personne
ne s'attendra à ce que :

std::vector< int > v( 10 ) ;
// initialiser...
std::copy( v.begin() + 3, v.begin() + 5, v.rbegin() + 5 ) ;

ait un comportement défini.

(Je sens qu'il y a une faiblesse dans la norme ici, mais je ne
sais pas l'exprimer mieux non plus. Peut-être quelque chose sur
des effets de l'expression *(result + i), for 0 <= i <
end-begin.)

--
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
Gabriel Dos Reis
"kanze" writes:

[...]

| > Mais je crois que cela n'est pas une implémentation générique
| > valide de std::copy(). La raison est que std::copy attend un
| > InputIterator : pour un tel iterateur, si tu fais une copie,
| > tu n'as plus d'assurance que l'itérateur copié est encore bon
| > pour faire quoi que ce soit (en particulier tester pour
| > égalité). Voir la table 72, en particulier la phrase
|
| > pre: r is dereferenceable.
| > post: r is dereferenceable or r is past-the-end.
| > post: any copies of the previous value of r are no longer
| > required either to be dereferenceable or to be in the domain
| > of ==.
|
| > Maintenant, si tu écris plusieurs versions de std::copy()
| > suivant la catégorie d'itérateurs, alors... Mains même là je
| > ne serais pas d'accord avec toi, parce que _It peut être
| > essentiellement un pointeur vers un noeud et dans ce cas, que
| > tu copies l'itérateur ou non, ne change rien à l'affaire.
|
| On parle d'un cas très précis

Je n'en doute pas une seule seconde. L'argument de Jean=Marc tourne
autour de l'implémentation de std::copy(), qui est décrite comme
attendant deux InputIterator et un OutputIterator ; et ce que tu peux
faire avec hypothèses seulement.

Comme je l'ai dit aileurs, il est possible de comprendre pourquoi ça
boucle sans revenir à une implémentation particulière : push_back()
insère toujours avant l.end().

[...]

| Dans la spécification de std::copy, il y a une contrainte :
| « result shall not be in the range [first, last). »

Oui, mais le back_inserter() n'est jamais dans cet intervalle. De fait,
c'est un proxy à quelque chose qui n'est pas un itérateur. Ce que fait le
back_inserter(), c'est qu'il modifie la longueur de l'intervalle à
chaque « *out = ... ».

| Je ne sais
| pas comment appliquer cette phrase au juste

elle ne s'applique pas, cherche pas.

| si result est un
| back_insertion_iterator, mais je crois qu'on pourrait bien
| arguer que l'intention est que cet appel soit invalide.

on peut tout arguer. Le point réel n'est pas vraiment de
l'argumentation, mais plutôt de la preuve de quelque chose est ou
n'est pas.

[...]

| (Je sens qu'il y a une faiblesse dans la norme ici, mais je ne
| sais pas l'exprimer mieux non plus. Peut-être quelque chose sur
| des effets de l'expression *(result + i), for 0 <= i <
| end-begin.)

La description des « requirements » est incomplète, ça tout le monde
le sait -- du moins, lorsqu'on suffisamment travaillé avec les
itérateurs et les algorithmes. Ce qui manque problement dans ec cas
particulier, c'est de supposer que l'expressions « *out++ = t »
ne modifie pas la longeur de la suite d'entrée, mais encore là...

-- Gaby
Avatar
Gabriel Dos Reis
"kanze" writes:

| Marc Boyer wrote:
| > > Marc Boyer writes:
| > >| Ils font une liste circulaire ?
|
| > > En quelque sorte. Mais un noeud dans cette liste «
| > > circulaire » sert à marquer end(), donc de l'extérieur ce
| > > n'est pas une liste circulaire.
|
| > > Mais on peut expliquer pourquoi le code ci-dessus va boucler
| > > indéfiniment (à condition que l.size() > 0) sans regarder
| > > une implémentation particulière.
|
| > A condition de comprendre que end() est modifié par le
| > push_back(), c'est à dire que end() dépend du contenu de la
| > liste.
|
| Dans le cas d'une liste, je ne crois pas.
|
| Une fonction comme push_back() ne modifie jamais d'itérateur.
| (C'est évident, si tu y penses. Il ne les connaît pas.)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

l.push_back(t) est décrite comme l.insert(l.end(), t). Alors, on peut
prétendre tout ce qu'on veut mais elle connaît, dans ce cas particulier,
l.end().

| Alors,
| si tu as un itérateur dans une collection, et que tu y fais
| push_back(), la « valeur » de ton itérateur ne change pas,

OK. Donc, tu comprends maintenant pourquoi le programme posté doit
boucler sans savoir qu'elle implémentation de std::copy() est utilisée.

[...]

| Donc, quelque chose comme :
|
| std::list< int >::iterator i = l.end() ;
| l.push_back( x ) ;
| assert( i == l.end() ) ;
|
| est garantie.

exact.

-- Gaby
Avatar
kanze
Gabriel Dos Reis wrote:
"kanze" writes:

[...]

| Dans la spécification de std::copy, il y a une contrainte :
| « result shall not be in the range [first, last). »

Oui, mais le back_inserter() n'est jamais dans cet intervalle.
De fait, c'est un proxy à quelque chose qui n'est pas un
itérateur. Ce que fait le back_inserter(), c'est qu'il modifie
la longueur de l'intervalle à chaque « *out = ... ».

| Je ne sais pas comment appliquer cette phrase au juste

elle ne s'applique pas, cherche pas.


C'est ce que je craignais.

Sauf dans le sens que c'est une précondition, et une
précondition s'applique toujours. Soit l'itérateur est dans
l'intervalle, on a violé la précondition, et il y a un
comportement indéfini, soit il ne l'est pas, et la fonction doit
marcher.

Comme toi, j'ai beaucoup de mal à considérer qu'un
back_insertion_iterator soit dans l'intervalle. Mais alors, la
fonction doit marcher. Reste à determiner ce que « marcher »
signifie dans ce cas-ci, mais une boucle sans fin, ce n'est pas
marcher, à mon avis.

| si result est un back_insertion_iterator, mais je crois
| qu'on pourrait bien arguer que l'intention est que cet appel
| soit invalide.

on peut tout arguer. Le point réel n'est pas vraiment de
l'argumentation, mais plutôt de la preuve de quelque chose est
ou n'est pas.


Disons que je cherchais plutôt à découvrir l'intention. Je crois
effectivement que les mots dans la norme ne suffissent pas.

[...]

| (Je sens qu'il y a une faiblesse dans la norme ici, mais je
| ne sais pas l'exprimer mieux non plus. Peut-être quelque
| chose sur des effets de l'expression *(result + i), for 0 <=
| i < end-begin.)

La description des « requirements » est incomplète, ça tout le
monde le sait -- du moins, lorsqu'on suffisamment travaillé
avec les itérateurs et les algorithmes. Ce qui manque
problement dans ec cas particulier, c'est de supposer que
l'expressions « *out++ = t » ne modifie pas la longeur de la
suite d'entrée, mais encore là...


On est d'accord, alors. (J'ai posté une question là-dessus dans
comp.std.c++. Parce que le problème se pose aussi avec des
adaptateurs d'itérateurs, de Boost.)

--
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
Gabriel Dos Reis
"kanze" writes:

| Gabriel Dos Reis wrote:
| > "kanze" writes:
|
| > [...]
|
| > | Dans la spécification de std::copy, il y a une contrainte :
| > | « result shall not be in the range [first, last). »
|
| > Oui, mais le back_inserter() n'est jamais dans cet intervalle.
| > De fait, c'est un proxy à quelque chose qui n'est pas un
| > itérateur. Ce que fait le back_inserter(), c'est qu'il modifie
| > la longueur de l'intervalle à chaque « *out = ... ».
|
| > | Je ne sais pas comment appliquer cette phrase au juste
|
| > elle ne s'applique pas, cherche pas.
|
| C'est ce que je craignais.
|
| Sauf dans le sens que c'est une précondition, et une
| précondition s'applique toujours.

Huh ?!?

| Soit l'itérateur est dans
| l'intervalle, on a violé la précondition, et il y a un
| comportement indéfini, soit il ne l'est pas, et la fonction doit
| marcher.
|
| Comme toi, j'ai beaucoup de mal à considérer qu'un
| back_insertion_iterator soit dans l'intervalle. Mais alors, la
| fonction doit marcher. Reste à determiner ce que « marcher »
| signifie dans ce cas-ci, mais une boucle sans fin, ce n'est pas
| marcher, à mon avis.

Si la boucle sans fin est la conclusion logique, alors elle marche.
Je comprends que tu n'aimes pas ce comportement, mais c'est différent
de « ce n'est pas marcher ».

[...]

| > La description des « requirements » est incomplète, ça tout le
| > monde le sait -- du moins, lorsqu'on suffisamment travaillé
| > avec les itérateurs et les algorithmes. Ce qui manque
| > problement dans ec cas particulier, c'est de supposer que
| > l'expressions « *out++ = t » ne modifie pas la longeur de la
| > suite d'entrée, mais encore là...
|
| On est d'accord, alors. (J'ai posté une question là-dessus dans
| comp.std.c++. Parce que le problème se pose aussi avec des
| adaptateurs d'itérateurs, de Boost.)

OK.

-- Gaby
2 3 4 5 6