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.

10 réponses

2 3 4 5 6
Avatar
Marc Boyer
Gabriel Dos Reis a écrit :
Marc Boyer writes:
| Henri de Solages a écrit :
| - pour les experts, est-ce qu'il ne faut pas considérer
| l'absence d'explicit comme un bug à rapporter à
| l'équipe de GCC ?

C'en est un. Peux-tu faire un bug report ? (mets moi en CC: du PR, mon
email chez GNU est gdr at gcc dot gnu dot org).


OK, je fais ça.
Je mets quoi comme version de gcc ? La mienne (3.3.5) est un peu
vieille. Je suppose que tu as toi testé avec une chose plus récente.

Marc Boyer
--
À vélo, prendre une rue à contre-sens est moins dangeureux
que prendre un boulevard dans le sens légal. À qui la faute ?

Avatar
Marc Boyer
kanze a écrit :
Marc Boyer wrote:

Depuis quand est-ce qu'il y a un pointeur NULL dans une
liste chaînée en C.


A l'école ?


Je sais que j'ai effectivement vu une telle technique dans des
livres. Mais que je n'ai jamais compris pourquoi -- la solution
avec la liste circulaire est tellement évidente, et bien plus
simple à coder ET plus rapide. Il ne m'est jamais venu à
l'ésprit d'en faire autrement.


C'est une bonne question. J'avoue que tout le monde fait
d'abord la liste NULL-terminated, que j'ai fait comme tout
le monde.

Peut-être c'est parce que quand j'ai abordé les listes chaînées
pour la première fois, on avait déjà assez de mémoire pour ne
pas être obligé à s'emmerder avec des listes à chaînage simple,
or que les auteurs des livres ont commencé avec des listes à
chaînage simple (où je ne vois guère d'autre solution qu'une
valeur sentinelle comme NULL), et ont simplement continué pareil
avec des listes à chaînage double, même si une solution plus
simple existe.


Je crois que ce qui serait difficile à faire comprendre
à des débutants, c'est que même dans une liste vide, on
met un élément, et que la valeur transportée par cet élément
on ne s'y intéresse pas.

De plus, pour un simple fonction comme 'inserer', c'est
déjà assez dur pour les débutantts en gérant un seul sens
de chainage, avec le double chainage, ça risque d'être
emmellé pour eux.
Mais en effet, dans un usage professionnel, je comprends
que la doublement chainée circulaire est plus simple.

Marc Boyer
--
À vélo, prendre une rue à contre-sens est moins dangeureux
que prendre un boulevard dans le sens légal. À qui la faute ?



Avatar
kanze
Marc Boyer wrote:

[...]
Peut-être c'est parce que quand j'ai abordé les listes
chaînées pour la première fois, on avait déjà assez de
mémoire pour ne pas être obligé à s'emmerder avec des listes
à chaînage simple, or que les auteurs des livres ont
commencé avec des listes à chaînage simple (où je ne vois
guère d'autre solution qu'une valeur sentinelle comme NULL),
et ont simplement continué pareil avec des listes à chaînage
double, même si une solution plus simple existe.


Je crois que ce qui serait difficile à faire comprendre à
des débutants, c'est que même dans une liste vide, on met un
élément, et que la valeur transportée par cet élément on ne
s'y intéresse pas.


Sauf que je n'y mets pas d'élément supplémentaire.

Mais évidemment, j'ai plein des cast, dans une classe générique.
Ce qui n'est peut-être pas ce qu'on veut montrer aux débuttants
non plus. Et que ça pourrait prêter à confusion que parfois mon
Node* est un Element*, et parfois (quand le Node est la racine)
non.

--
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
Marc Boyer
kanze a écrit :
Marc Boyer wrote:
Peut-être c'est parce que quand j'ai abordé les listes
chaînées pour la première fois, on avait déjà assez de
mémoire pour ne pas être obligé à s'emmerder avec des listes
à chaînage simple, or que les auteurs des livres ont
commencé avec des listes à chaînage simple (où je ne vois
guère d'autre solution qu'une valeur sentinelle comme NULL),
et ont simplement continué pareil avec des listes à chaînage
double, même si une solution plus simple existe.


Je crois que ce qui serait difficile à faire comprendre à
des débutants, c'est que même dans une liste vide, on met un
élément, et que la valeur transportée par cet élément on ne
s'y intéresse pas.


Sauf que je n'y mets pas d'élément supplémentaire.


Toi non, mais dans un cours de débutant, si.

Mais évidemment, j'ai plein des cast, dans une classe générique.
Ce qui n'est peut-être pas ce qu'on veut montrer aux débuttants
non plus. Et que ça pourrait prêter à confusion que parfois mon
Node* est un Element*, et parfois (quand le Node est la racine)
non.


Tout à fait.

Marc Boyer
--
À vélo, prendre une rue à contre-sens est moins dangeureux
que prendre un boulevard dans le sens légal. À qui la faute ?



Avatar
kanze
Gabriel Dos Reis wrote:
Marc Boyer writes:

[...]

| > Je crois. Je crois qu'il font à peu près comme j'ai fait
| > ci-dessus. J'ai l'impression que la seule différence entre
| > leur code et ce que je faisais auparavent (depuis au moins
| > vingt ans maintenant), c'est qu'ils allouent la racine
| > dynamiquement,

| Oui.

Non, il n'y a pas d'allocation dynamique de la racine.

| La classe possède un champ _M_node, qui semble être

struct _List_impl
: public _Node_Alloc_type
{
_List_node_base _M_node;
_List_impl (const _Node_Alloc_type& __a)
: _Node_Alloc_type(__a)
{ }
};

Cette racine n'est pas allouée dynamiquement.

| cette racine (d'ailleurs, end() se contente de retourner
| _M_node et begin() retourne _M_node->next), et le constructeur
| fait
| _M_node= _M_get_node();

Le constructeur fait ceci :

_List_base(const allocator_type& __a)
: _M_impl(__a)
{ _M_init(); }

Et _M_init() est:

void
_M_init()
{
this->_M_impl._M_node._M_next = &this->_M_impl._M_node;
this->_M_impl._M_node._M_prev = &this->_M_impl._M_node;
}

Je ne vois toujours pas d'allocation dynamique.


En fait, ça ressemble drôlement à ce que je faisais. Même
jusqu'aux noms next et prev.

Mais je crois que ça dépend de la version. Je me rappelle bien
avoir eu un bad_alloc une fois d'un constructeur de std::list.
Avec g++, mais je ne sais pas quelle version. (Vue le temps
depuis, c'était probablement 2.95.2. Ce qui veut dire que le
code a beaucoup évolué depuis.)

--
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
Jean-Marc Bourguet wrote:
"kanze" writes:

Peut-être c'est parce que quand j'ai abordé les listes
chaînées pour la première fois, on avait déjà assez de
mémoire pour ne pas être obligé à s'emmerder avec des listes
à chaînage simple, or que les auteurs des livres ont
commencé avec des listes à chaînage simple (où je ne vois
guère d'autre solution qu'une valeur sentinelle comme NULL),
et ont simplement continué pareil avec des listes à chaînage
double, même si une solution plus simple existe.


Ca depend du contexte, utiliser NULL peut etre utile si tu
dois parcourir la fin de la liste et que tu n'as pas acces au
noeud special pour t'en servir de sentinelle (par exemple si
ta liste est invasive).


Et comment est-ce que tu trouves le debut de la liste, si tu ne
connais pas le noeud spécial ?

C'est surtout avec les listes invasives que j'aime cette
technique. Parce qu'il me permet d'enlever l'élément de la
liste dans son destructeur, sans que l'élément lui-même sache
dans quelle liste il se trouve. Genre :

Node::~Node()
{
next->prec = prec ;
prec->next = next ;
}

--
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
Marc Boyer writes:

| Gabriel Dos Reis a écrit :
| > Marc Boyer writes:
| >| Henri de Solages a écrit :
| >| - pour les experts, est-ce qu'il ne faut pas considérer
| >| l'absence d'explicit comme un bug à rapporter à
| >| l'équipe de GCC ?
| >
| > C'en est un. Peux-tu faire un bug report ? (mets moi en CC: du PR, mon
| > email chez GNU est gdr at gcc dot gnu dot org).
|
| OK, je fais ça.
| Je mets quoi comme version de gcc ? La mienne (3.3.5) est un peu

3.4.x et 4.0.x -- 3.3.x n'est plus maintenue (j'ai fermé cette boutique là).

Merci.

-- Gaby
Avatar
Marc Boyer
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.

Marc Boyer
--
À vélo, prendre une rue à contre-sens est moins dangeureux
que prendre un boulevard dans le sens légal. À qui la faute ?

Avatar
Jean-Marc Bourguet
"kanze" writes:

Jean-Marc Bourguet wrote:
"kanze" writes:

Peut-être c'est parce que quand j'ai abordé les listes
chaînées pour la première fois, on avait déjà assez de
mémoire pour ne pas être obligé à s'emmerder avec des listes
à chaînage simple, or que les auteurs des livres ont
commencé avec des listes à chaînage simple (où je ne vois
guère d'autre solution qu'une valeur sentinelle comme NULL),
et ont simplement continué pareil avec des listes à chaînage
double, même si une solution plus simple existe.


Ca depend du contexte, utiliser NULL peut etre utile si tu
dois parcourir la fin de la liste et que tu n'as pas acces au
noeud special pour t'en servir de sentinelle (par exemple si
ta liste est invasive).


Et comment est-ce que tu trouves le debut de la liste, si tu ne
connais pas le noeud spécial ?

C'est surtout avec les listes invasives que j'aime cette
technique. Parce qu'il me permet d'enlever l'élément de la
liste dans son destructeur, sans que l'élément lui-même sache
dans quelle liste il se trouve. Genre :

Node::~Node()
{
next->prec = prec ;
prec->next = next ;
}


J'ai bien compris, je l'utilise aussi. Mais il y a des cas ou tu
parts uniquement de ton noeud (comme dans ton exemple pour
l'effacement) et tu dois agir sur les autres elements de la liste (ou
juste ceux qui suivent). Avec ton noeud special que tu ne sais
special que si tu connais la liste, tu as un probleme... Avec NULL, tu
as un probleme pour l'effacement sans connaitre la liste.

Si tu as besoin des deux operations sans connaitre la liste, tu peux
marquer le noeud special en ne lui donnant pas de predecesseur. (Le
dernier noeud n'a pas de successeur -- tu peux eventuellement avoir
comme dernier noeud un noeud sentinelle aussi, qui lui n'a alors pas
de successeur, on a alors exactement la meme complexite de code pour
l'insertion et l'effacedement d'un noeud qu'avec ta technique, et en
plus on peut iterer en partant d'un noeud donne. Le cout est bien sur
les deux sentinelles a la place d'une seule).

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
Jean-Marc Bourguet
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

--
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


2 3 4 5 6