dans le cadre d'unn projet en C, j'en suis ammené à m'interroger
sur l'implémentation des itérateurs end/rend dans list et map.
En effet, dans ces deux conteneurs, une impleméntation triviale d'un
itérateur est de faire un pointeur sur un élément interne (cellule
pour list et noeud pour map), avec une surcharge de * et ->.
Mais, comment coder end (et rend), en restant homogène ?
J'ai plusieurs idées en tête, mais la plus réaliste est aussi
la plus moche :-(
1) Ajouter des éléments end/rend
Ajouter dans la liste et dans l'arbre des éléments "en plus",
des cellules/noeuds marqueurs de fin. Mais autant c'est faisable
pour la liste (avec des tests genre it->next->next != NULL
au lieu de it->next != NULL), autant dans l'arbre, ça
complique tous les codes.
2) Itérateur comme tuple (pointeur element, pointeur conteneur)
Mettre dans un itérateur un pointeur sur le conteneur lui
même, ainsi, on peu imaginer que (NULL, p1) est le résultat
de p1->end().
A priori, je me dirige vers 2), surtout pour l'arbre de map,
mais j'ai peut-être raté un truc.
J'ai tenté de jetter un coup d'oeuil sur les sources
de la stl de gcc, et les itérateurs semblent n'etre qu'un
pointeur.
Le list semble codé avec un element en rab, en
tête (?), end() étant l'adresse de ce pointeur, et
begin étant end()->next (en gros). Pour l'arbre, ça semble
être la racine,
mais j'ai pas compris comment ils font si
par hasard on fait un erase() de cette racine...
dans le cadre d'unn projet en C, j'en suis ammené à m'interroger
sur l'implémentation des itérateurs end/rend dans list et map.
En effet, dans ces deux conteneurs, une impleméntation triviale d'un
itérateur est de faire un pointeur sur un élément interne (cellule
pour list et noeud pour map), avec une surcharge de * et ->.
Mais, comment coder end (et rend), en restant homogène ?
J'ai plusieurs idées en tête, mais la plus réaliste est aussi
la plus moche :-(
1) Ajouter des éléments end/rend
Ajouter dans la liste et dans l'arbre des éléments "en plus",
des cellules/noeuds marqueurs de fin. Mais autant c'est faisable
pour la liste (avec des tests genre it->next->next != NULL
au lieu de it->next != NULL), autant dans l'arbre, ça
complique tous les codes.
2) Itérateur comme tuple (pointeur element, pointeur conteneur)
Mettre dans un itérateur un pointeur sur le conteneur lui
même, ainsi, on peu imaginer que (NULL, p1) est le résultat
de p1->end().
A priori, je me dirige vers 2), surtout pour l'arbre de map,
mais j'ai peut-être raté un truc.
J'ai tenté de jetter un coup d'oeuil sur les sources
de la stl de gcc, et les itérateurs semblent n'etre qu'un
pointeur.
Le list semble codé avec un element en rab, en
tête (?), end() étant l'adresse de ce pointeur, et
begin étant end()->next (en gros). Pour l'arbre, ça semble
être la racine,
mais j'ai pas compris comment ils font si
par hasard on fait un erase() de cette racine...
dans le cadre d'unn projet en C, j'en suis ammené à m'interroger
sur l'implémentation des itérateurs end/rend dans list et map.
En effet, dans ces deux conteneurs, une impleméntation triviale d'un
itérateur est de faire un pointeur sur un élément interne (cellule
pour list et noeud pour map), avec une surcharge de * et ->.
Mais, comment coder end (et rend), en restant homogène ?
J'ai plusieurs idées en tête, mais la plus réaliste est aussi
la plus moche :-(
1) Ajouter des éléments end/rend
Ajouter dans la liste et dans l'arbre des éléments "en plus",
des cellules/noeuds marqueurs de fin. Mais autant c'est faisable
pour la liste (avec des tests genre it->next->next != NULL
au lieu de it->next != NULL), autant dans l'arbre, ça
complique tous les codes.
2) Itérateur comme tuple (pointeur element, pointeur conteneur)
Mettre dans un itérateur un pointeur sur le conteneur lui
même, ainsi, on peu imaginer que (NULL, p1) est le résultat
de p1->end().
A priori, je me dirige vers 2), surtout pour l'arbre de map,
mais j'ai peut-être raté un truc.
J'ai tenté de jetter un coup d'oeuil sur les sources
de la stl de gcc, et les itérateurs semblent n'etre qu'un
pointeur.
Le list semble codé avec un element en rab, en
tête (?), end() étant l'adresse de ce pointeur, et
begin étant end()->next (en gros). Pour l'arbre, ça semble
être la racine,
mais j'ai pas compris comment ils font si
par hasard on fait un erase() de cette racine...
Marc Boyer writes:En effet, dans ces deux conteneurs, une impleméntation triviale d'un
itérateur est de faire un pointeur sur un élément interne (cellule
pour list et noeud pour map), avec une surcharge de * et ->.
Lorsque tu dis « faire un pointeur ... », j'imagine que tu veux dire
« définir une structure contenant un pointeur ... ». Il faut
également surcharger les opérateurs de comparaison, d'incrémentation
et de décrémentation.
Mais, comment coder end (et rend), en restant homogène ?
Qu'entends-tu par « homogène » ?
Ce qui me vient directement à l'esprit serait d'initialiser le
pointeur membre de l'itérateur à une valeur sentinelle, comme 0. Je
ne sais pas s'il s'agit d'une implémentation permise, mais je ne vois
pas a priori de contre-indication, ni d'avantage à avoir une autre
valeur.
1) Ajouter des éléments end/rend
Ajouter dans la liste et dans l'arbre des éléments "en plus",
des cellules/noeuds marqueurs de fin. Mais autant c'est faisable
pour la liste (avec des tests genre it->next->next != NULL
au lieu de it->next != NULL), autant dans l'arbre, ça
complique tous les codes.
Je ne vois pas l'intérêt.
J'ai tenté de jetter un coup d'oeuil sur les sources
de la stl de gcc, et les itérateurs semblent n'etre qu'un
pointeur.
Huh ? Autant je comprendrais avec std::vector (et il me semble
qu'il s'agissait effectivement d'une implémentation courante), autant
je n'arrive pas à voir comment utiliser un simple pointeur comme
itérateur de std::list ou std::map.
L'itérateur renvoyé par end() désigne donc un noeud existant, mais
bidon ?
On a donc une certaine identité entre chaque itérateur
retourné par end(), depuis une même liste ou map ...
mais j'ai pas compris comment ils font si
par hasard on fait un erase() de cette racine...
Tu parles bien de o.erase( o.end() ) ?!?
--drkm, en recherche d'un stage : http://www.fgeorges.org/ipl/stage.html
Marc Boyer <Marc.Boyer@enseeiht.yahoo.fr.invalid> writes:
En effet, dans ces deux conteneurs, une impleméntation triviale d'un
itérateur est de faire un pointeur sur un élément interne (cellule
pour list et noeud pour map), avec une surcharge de * et ->.
Lorsque tu dis « faire un pointeur ... », j'imagine que tu veux dire
« définir une structure contenant un pointeur ... ». Il faut
également surcharger les opérateurs de comparaison, d'incrémentation
et de décrémentation.
Mais, comment coder end (et rend), en restant homogène ?
Qu'entends-tu par « homogène » ?
Ce qui me vient directement à l'esprit serait d'initialiser le
pointeur membre de l'itérateur à une valeur sentinelle, comme 0. Je
ne sais pas s'il s'agit d'une implémentation permise, mais je ne vois
pas a priori de contre-indication, ni d'avantage à avoir une autre
valeur.
1) Ajouter des éléments end/rend
Ajouter dans la liste et dans l'arbre des éléments "en plus",
des cellules/noeuds marqueurs de fin. Mais autant c'est faisable
pour la liste (avec des tests genre it->next->next != NULL
au lieu de it->next != NULL), autant dans l'arbre, ça
complique tous les codes.
Je ne vois pas l'intérêt.
J'ai tenté de jetter un coup d'oeuil sur les sources
de la stl de gcc, et les itérateurs semblent n'etre qu'un
pointeur.
Huh ? Autant je comprendrais avec std::vector (et il me semble
qu'il s'agissait effectivement d'une implémentation courante), autant
je n'arrive pas à voir comment utiliser un simple pointeur comme
itérateur de std::list ou std::map.
L'itérateur renvoyé par end() désigne donc un noeud existant, mais
bidon ?
On a donc une certaine identité entre chaque itérateur
retourné par end(), depuis une même liste ou map ...
mais j'ai pas compris comment ils font si
par hasard on fait un erase() de cette racine...
Tu parles bien de o.erase( o.end() ) ?!?
--drkm, en recherche d'un stage : http://www.fgeorges.org/ipl/stage.html
Marc Boyer writes:En effet, dans ces deux conteneurs, une impleméntation triviale d'un
itérateur est de faire un pointeur sur un élément interne (cellule
pour list et noeud pour map), avec une surcharge de * et ->.
Lorsque tu dis « faire un pointeur ... », j'imagine que tu veux dire
« définir une structure contenant un pointeur ... ». Il faut
également surcharger les opérateurs de comparaison, d'incrémentation
et de décrémentation.
Mais, comment coder end (et rend), en restant homogène ?
Qu'entends-tu par « homogène » ?
Ce qui me vient directement à l'esprit serait d'initialiser le
pointeur membre de l'itérateur à une valeur sentinelle, comme 0. Je
ne sais pas s'il s'agit d'une implémentation permise, mais je ne vois
pas a priori de contre-indication, ni d'avantage à avoir une autre
valeur.
1) Ajouter des éléments end/rend
Ajouter dans la liste et dans l'arbre des éléments "en plus",
des cellules/noeuds marqueurs de fin. Mais autant c'est faisable
pour la liste (avec des tests genre it->next->next != NULL
au lieu de it->next != NULL), autant dans l'arbre, ça
complique tous les codes.
Je ne vois pas l'intérêt.
J'ai tenté de jetter un coup d'oeuil sur les sources
de la stl de gcc, et les itérateurs semblent n'etre qu'un
pointeur.
Huh ? Autant je comprendrais avec std::vector (et il me semble
qu'il s'agissait effectivement d'une implémentation courante), autant
je n'arrive pas à voir comment utiliser un simple pointeur comme
itérateur de std::list ou std::map.
L'itérateur renvoyé par end() désigne donc un noeud existant, mais
bidon ?
On a donc une certaine identité entre chaque itérateur
retourné par end(), depuis une même liste ou map ...
mais j'ai pas compris comment ils font si
par hasard on fait un erase() de cette racine...
Tu parles bien de o.erase( o.end() ) ?!?
--drkm, en recherche d'un stage : http://www.fgeorges.org/ipl/stage.html
drkm wrote:Marc Boyer writes:En effet, dans ces deux conteneurs, une impleméntation triviale d'un
itérateur est de faire un pointeur sur un élément interne (cellule
pour list et noeud pour map), avec une surcharge de * et ->.
Lorsque tu dis « faire un pointeur ... », j'imagine que tu veux dire
« définir une structure contenant un pointeur ... ». Il faut
également surcharger les opérateurs de comparaison, d'incrémentation
et de décrémentation.
Tout a fait.Mais, comment coder end (et rend), en restant homogène ?
Qu'entends-tu par « homogène » ?
Pas un horible cast de la mort pour mettre un pointeur
sur conteneur dans une variable destinée à recevoir un pointeur
sur noeud interne.
Ce qui me vient directement à l'esprit serait d'initialiser le
pointeur membre de l'itérateur à une valeur sentinelle, comme 0. Je
ne sais pas s'il s'agit d'une implémentation permise, mais je ne vois
pas a priori de contre-indication, ni d'avantage à avoir une autre
valeur.
C'était aussi mon idée, facile à implémenter, faite, sauf que,
comment faire end()-1 et retrouver begin()+(size()-1) ?
1) Ajouter des éléments end/rend
Ajouter dans la liste et dans l'arbre des éléments "en plus",
des cellules/noeuds marqueurs de fin. Mais autant c'est faisable
pour la liste (avec des tests genre it->next->next != NULL
au lieu de it->next != NULL), autant dans l'arbre, ça
complique tous les codes.
Je ne vois pas l'intérêt.
De end(), on passe facilement à end()-1
J'ai tenté de jetter un coup d'oeuil sur les sources
de la stl de gcc, et les itérateurs semblent n'etre qu'un
pointeur.
Huh ? Autant je comprendrais avec std::vector (et il me semble
qu'il s'agissait effectivement d'une implémentation courante), autant
je n'arrive pas à voir comment utiliser un simple pointeur comme
itérateur de std::list ou std::map.
Pointeur sur un noeud interne. Globalement, une list<T>, c'est
un list_node<T>* avec
class list_node<T> { T data; list_node<T> *prev, *next; }
L'itérateur renvoyé par end() désigne donc un noeud existant, mais
bidon ?
Oui.On a donc une certaine identité entre chaque itérateur
retourné par end(), depuis une même liste ou map ...
Ben, heureusement non ?
mais j'ai pas compris comment ils font si
par hasard on fait un erase() de cette racine...
Tu parles bien de o.erase( o.end() ) ?!?
Non, dans un arbre équilibré, normalement, la racine c'est
pas o.end() mais plutôt o.begin()+ o.size()/2.
--drkm, en recherche d'un stage : http://www.fgeorges.org/ipl/stage.html
Désolé, je ne suis pas bruxellois...
drkm wrote:
Marc Boyer <Marc.Boyer@enseeiht.yahoo.fr.invalid> writes:
En effet, dans ces deux conteneurs, une impleméntation triviale d'un
itérateur est de faire un pointeur sur un élément interne (cellule
pour list et noeud pour map), avec une surcharge de * et ->.
Lorsque tu dis « faire un pointeur ... », j'imagine que tu veux dire
« définir une structure contenant un pointeur ... ». Il faut
également surcharger les opérateurs de comparaison, d'incrémentation
et de décrémentation.
Tout a fait.
Mais, comment coder end (et rend), en restant homogène ?
Qu'entends-tu par « homogène » ?
Pas un horible cast de la mort pour mettre un pointeur
sur conteneur dans une variable destinée à recevoir un pointeur
sur noeud interne.
Ce qui me vient directement à l'esprit serait d'initialiser le
pointeur membre de l'itérateur à une valeur sentinelle, comme 0. Je
ne sais pas s'il s'agit d'une implémentation permise, mais je ne vois
pas a priori de contre-indication, ni d'avantage à avoir une autre
valeur.
C'était aussi mon idée, facile à implémenter, faite, sauf que,
comment faire end()-1 et retrouver begin()+(size()-1) ?
1) Ajouter des éléments end/rend
Ajouter dans la liste et dans l'arbre des éléments "en plus",
des cellules/noeuds marqueurs de fin. Mais autant c'est faisable
pour la liste (avec des tests genre it->next->next != NULL
au lieu de it->next != NULL), autant dans l'arbre, ça
complique tous les codes.
Je ne vois pas l'intérêt.
De end(), on passe facilement à end()-1
J'ai tenté de jetter un coup d'oeuil sur les sources
de la stl de gcc, et les itérateurs semblent n'etre qu'un
pointeur.
Huh ? Autant je comprendrais avec std::vector (et il me semble
qu'il s'agissait effectivement d'une implémentation courante), autant
je n'arrive pas à voir comment utiliser un simple pointeur comme
itérateur de std::list ou std::map.
Pointeur sur un noeud interne. Globalement, une list<T>, c'est
un list_node<T>* avec
class list_node<T> { T data; list_node<T> *prev, *next; }
L'itérateur renvoyé par end() désigne donc un noeud existant, mais
bidon ?
Oui.
On a donc une certaine identité entre chaque itérateur
retourné par end(), depuis une même liste ou map ...
Ben, heureusement non ?
mais j'ai pas compris comment ils font si
par hasard on fait un erase() de cette racine...
Tu parles bien de o.erase( o.end() ) ?!?
Non, dans un arbre équilibré, normalement, la racine c'est
pas o.end() mais plutôt o.begin()+ o.size()/2.
--drkm, en recherche d'un stage : http://www.fgeorges.org/ipl/stage.html
Désolé, je ne suis pas bruxellois...
drkm wrote:Marc Boyer writes:En effet, dans ces deux conteneurs, une impleméntation triviale d'un
itérateur est de faire un pointeur sur un élément interne (cellule
pour list et noeud pour map), avec une surcharge de * et ->.
Lorsque tu dis « faire un pointeur ... », j'imagine que tu veux dire
« définir une structure contenant un pointeur ... ». Il faut
également surcharger les opérateurs de comparaison, d'incrémentation
et de décrémentation.
Tout a fait.Mais, comment coder end (et rend), en restant homogène ?
Qu'entends-tu par « homogène » ?
Pas un horible cast de la mort pour mettre un pointeur
sur conteneur dans une variable destinée à recevoir un pointeur
sur noeud interne.
Ce qui me vient directement à l'esprit serait d'initialiser le
pointeur membre de l'itérateur à une valeur sentinelle, comme 0. Je
ne sais pas s'il s'agit d'une implémentation permise, mais je ne vois
pas a priori de contre-indication, ni d'avantage à avoir une autre
valeur.
C'était aussi mon idée, facile à implémenter, faite, sauf que,
comment faire end()-1 et retrouver begin()+(size()-1) ?
1) Ajouter des éléments end/rend
Ajouter dans la liste et dans l'arbre des éléments "en plus",
des cellules/noeuds marqueurs de fin. Mais autant c'est faisable
pour la liste (avec des tests genre it->next->next != NULL
au lieu de it->next != NULL), autant dans l'arbre, ça
complique tous les codes.
Je ne vois pas l'intérêt.
De end(), on passe facilement à end()-1
J'ai tenté de jetter un coup d'oeuil sur les sources
de la stl de gcc, et les itérateurs semblent n'etre qu'un
pointeur.
Huh ? Autant je comprendrais avec std::vector (et il me semble
qu'il s'agissait effectivement d'une implémentation courante), autant
je n'arrive pas à voir comment utiliser un simple pointeur comme
itérateur de std::list ou std::map.
Pointeur sur un noeud interne. Globalement, une list<T>, c'est
un list_node<T>* avec
class list_node<T> { T data; list_node<T> *prev, *next; }
L'itérateur renvoyé par end() désigne donc un noeud existant, mais
bidon ?
Oui.On a donc une certaine identité entre chaque itérateur
retourné par end(), depuis une même liste ou map ...
Ben, heureusement non ?
mais j'ai pas compris comment ils font si
par hasard on fait un erase() de cette racine...
Tu parles bien de o.erase( o.end() ) ?!?
Non, dans un arbre équilibré, normalement, la racine c'est
pas o.end() mais plutôt o.begin()+ o.size()/2.
--drkm, en recherche d'un stage : http://www.fgeorges.org/ipl/stage.html
Désolé, je ne suis pas bruxellois...
Pas un horible cast de la mort pour mettre un pointeur
sur conteneur dans une variable destinée à recevoir un pointeur
sur noeud interne.
Je ne comprend toujours pas. Pourquoi veux-tu dans ce cas effectuer
un cast depuis un pointeur sur conteneur ?
Ce qui me vient directement à l'esprit serait d'initialiser le
pointeur membre de l'itérateur à une valeur sentinelle, comme 0. Je
ne sais pas s'il s'agit d'une implémentation permise, mais je ne vois
pas a priori de contre-indication, ni d'avantage à avoir une autre
valeur.
C'était aussi mon idée, facile à implémenter, faite, sauf que,
comment faire end()-1 et retrouver begin()+(size()-1) ?
std::list<>::end() - 1 ?!?
1) Ajouter des éléments end/rend
Ajouter dans la liste et dans l'arbre des éléments "en plus",
des cellules/noeuds marqueurs de fin. Mais autant c'est faisable
pour la liste (avec des tests genre it->next->next != NULL
au lieu de it->next != NULL), autant dans l'arbre, ça
complique tous les codes.
Je ne vois pas l'intérêt.
De end(), on passe facilement à end()-1
Mais je ne vois pas l'intérêt dans le cas de std::list<> et
std::map<>.
Pointeur sur un noeud interne. Globalement, une list<T>, c'est
un list_node<T>* avec
class list_node<T> { T data; list_node<T> *prev, *next; }
Je ne vois toujours pas comment il est possible d'utiliser un simple
pointeur, de quelque type que ce soit, comme itérateur dans le cas
d'une std::list<>.
Il me semblait que la seule contrainte de cet itérateur est d'être
comparé à un itérateur de ce conteneur. Un itérateur de fin qui
stockerait par exemple 0 comme valeur sentinelle me semblerait
acceptable (il suffit que le suivant du dernier noeud de la liste
vale lui-même 0). Mais, sans doute, je ne pense pas à certaines
contraintes ou garanties sur les itérateurs.
Pas un horible cast de la mort pour mettre un pointeur
sur conteneur dans une variable destinée à recevoir un pointeur
sur noeud interne.
Je ne comprend toujours pas. Pourquoi veux-tu dans ce cas effectuer
un cast depuis un pointeur sur conteneur ?
Ce qui me vient directement à l'esprit serait d'initialiser le
pointeur membre de l'itérateur à une valeur sentinelle, comme 0. Je
ne sais pas s'il s'agit d'une implémentation permise, mais je ne vois
pas a priori de contre-indication, ni d'avantage à avoir une autre
valeur.
C'était aussi mon idée, facile à implémenter, faite, sauf que,
comment faire end()-1 et retrouver begin()+(size()-1) ?
std::list<>::end() - 1 ?!?
1) Ajouter des éléments end/rend
Ajouter dans la liste et dans l'arbre des éléments "en plus",
des cellules/noeuds marqueurs de fin. Mais autant c'est faisable
pour la liste (avec des tests genre it->next->next != NULL
au lieu de it->next != NULL), autant dans l'arbre, ça
complique tous les codes.
Je ne vois pas l'intérêt.
De end(), on passe facilement à end()-1
Mais je ne vois pas l'intérêt dans le cas de std::list<> et
std::map<>.
Pointeur sur un noeud interne. Globalement, une list<T>, c'est
un list_node<T>* avec
class list_node<T> { T data; list_node<T> *prev, *next; }
Je ne vois toujours pas comment il est possible d'utiliser un simple
pointeur, de quelque type que ce soit, comme itérateur dans le cas
d'une std::list<>.
Il me semblait que la seule contrainte de cet itérateur est d'être
comparé à un itérateur de ce conteneur. Un itérateur de fin qui
stockerait par exemple 0 comme valeur sentinelle me semblerait
acceptable (il suffit que le suivant du dernier noeud de la liste
vale lui-même 0). Mais, sans doute, je ne pense pas à certaines
contraintes ou garanties sur les itérateurs.
Pas un horible cast de la mort pour mettre un pointeur
sur conteneur dans une variable destinée à recevoir un pointeur
sur noeud interne.
Je ne comprend toujours pas. Pourquoi veux-tu dans ce cas effectuer
un cast depuis un pointeur sur conteneur ?
Ce qui me vient directement à l'esprit serait d'initialiser le
pointeur membre de l'itérateur à une valeur sentinelle, comme 0. Je
ne sais pas s'il s'agit d'une implémentation permise, mais je ne vois
pas a priori de contre-indication, ni d'avantage à avoir une autre
valeur.
C'était aussi mon idée, facile à implémenter, faite, sauf que,
comment faire end()-1 et retrouver begin()+(size()-1) ?
std::list<>::end() - 1 ?!?
1) Ajouter des éléments end/rend
Ajouter dans la liste et dans l'arbre des éléments "en plus",
des cellules/noeuds marqueurs de fin. Mais autant c'est faisable
pour la liste (avec des tests genre it->next->next != NULL
au lieu de it->next != NULL), autant dans l'arbre, ça
complique tous les codes.
Je ne vois pas l'intérêt.
De end(), on passe facilement à end()-1
Mais je ne vois pas l'intérêt dans le cas de std::list<> et
std::map<>.
Pointeur sur un noeud interne. Globalement, une list<T>, c'est
un list_node<T>* avec
class list_node<T> { T data; list_node<T> *prev, *next; }
Je ne vois toujours pas comment il est possible d'utiliser un simple
pointeur, de quelque type que ce soit, comme itérateur dans le cas
d'une std::list<>.
Il me semblait que la seule contrainte de cet itérateur est d'être
comparé à un itérateur de ce conteneur. Un itérateur de fin qui
stockerait par exemple 0 comme valeur sentinelle me semblerait
acceptable (il suffit que le suivant du dernier noeud de la liste
vale lui-même 0). Mais, sans doute, je ne pense pas à certaines
contraintes ou garanties sur les itérateurs.
J'ai tenté de jetter un coup d'oeuil sur les sources
de la stl de gcc, et les itérateurs semblent n'etre qu'un
pointeur. Le list semble codé avec un element en rab, en
tête (?), end() étant l'adresse de ce pointeur, et
begin étant end()->next (en gros). Pour l'arbre, ça semble
être la racine, mais j'ai pas compris comment ils font si
par hasard on fait un erase() de cette racine...
J'ai tenté de jetter un coup d'oeuil sur les sources
de la stl de gcc, et les itérateurs semblent n'etre qu'un
pointeur. Le list semble codé avec un element en rab, en
tête (?), end() étant l'adresse de ce pointeur, et
begin étant end()->next (en gros). Pour l'arbre, ça semble
être la racine, mais j'ai pas compris comment ils font si
par hasard on fait un erase() de cette racine...
J'ai tenté de jetter un coup d'oeuil sur les sources
de la stl de gcc, et les itérateurs semblent n'etre qu'un
pointeur. Le list semble codé avec un element en rab, en
tête (?), end() étant l'adresse de ce pointeur, et
begin étant end()->next (en gros). Pour l'arbre, ça semble
être la racine, mais j'ai pas compris comment ils font si
par hasard on fait un erase() de cette racine...
In article , drkm wrote:Pas un horible cast de la mort pour mettre un pointeur
sur conteneur dans une variable destinée à recevoir un pointeur
sur noeud interne.
Je ne comprend toujours pas. Pourquoi veux-tu dans ce cas effectuer
un cast depuis un pointeur sur conteneur ?
A partir de end(), il faut pouvoir retrouver le conteneur,
donc,
pointer sur quelque chose. Hors, on ne peut pas pointer sur un element
puisque les iterateurs ne doivent pas etre invalidé par la
destruction d'un élément désigné par un autre itérateur.
Sur quoi pointer donc ?Ce qui me vient directement à l'esprit serait d'initialiser le
pointeur membre de l'itérateur à une valeur sentinelle, comme 0. Je
ne sais pas s'il s'agit d'une implémentation permise, mais je ne vois
pas a priori de contre-indication, ni d'avantage à avoir une autre
valeur.
C'était aussi mon idée, facile à implémenter, faite, sauf que,
comment faire end()-1 et retrouver begin()+(size()-1) ?
std::list<>::end() - 1 ?!?
Me semble bien qu'on peut décrémenter l'intérateur end, non ?
#include <list>
int main(){
std::list<int> li;
std::list<int>::iterator it;
li.push_back(0);
it= li.end();
it--;
assert( it == li.begin() );
return 0;
}
Ou ça marche juste parce que mon compilateur est gentil avec moi ?
1) Ajouter des éléments end/rend
Ajouter dans la liste et dans l'arbre des éléments "en plus",
des cellules/noeuds marqueurs de fin. Mais autant c'est faisable
pour la liste (avec des tests genre it->next->next != NULL
au lieu de it->next != NULL), autant dans l'arbre, ça
complique tous les codes.
Je ne vois pas l'intérêt.
De end(), on passe facilement à end()-1
Mais je ne vois pas l'intérêt dans le cas de std::list<> et
std::map<>.
Ben, c'est *une* solution.
Pointeur sur un noeud interne. Globalement, une list<T>, c'est
un list_node<T>* avec
class list_node<T> { T data; list_node<T> *prev, *next; }
Je ne vois toujours pas comment il est possible d'utiliser un simple
pointeur, de quelque type que ce soit, comme itérateur dans le cas
d'une std::list<>.
Quand je dis "simple pointeur", c'est que l'unique variable
membre est un poiteur, auquel on rajoute toutes les fonctions qui
vont autour (*, ->, l'arithmétique...)
Il me semblait que la seule contrainte de cet itérateur est d'être
comparé à un itérateur de ce conteneur. Un itérateur de fin qui
stockerait par exemple 0 comme valeur sentinelle me semblerait
acceptable (il suffit que le suivant du dernier noeud de la liste
vale lui-même 0). Mais, sans doute, je ne pense pas à certaines
contraintes ou garanties sur les itérateurs.
Ben, faire
it= li.end();
it--;
Mais est-ce valide ? Je croyais que oui mais je me plante
peut-être.
In article <wkbri63jx5.fsf@fgeorges.org>, drkm wrote:
Pas un horible cast de la mort pour mettre un pointeur
sur conteneur dans une variable destinée à recevoir un pointeur
sur noeud interne.
Je ne comprend toujours pas. Pourquoi veux-tu dans ce cas effectuer
un cast depuis un pointeur sur conteneur ?
A partir de end(), il faut pouvoir retrouver le conteneur,
donc,
pointer sur quelque chose. Hors, on ne peut pas pointer sur un element
puisque les iterateurs ne doivent pas etre invalidé par la
destruction d'un élément désigné par un autre itérateur.
Sur quoi pointer donc ?
Ce qui me vient directement à l'esprit serait d'initialiser le
pointeur membre de l'itérateur à une valeur sentinelle, comme 0. Je
ne sais pas s'il s'agit d'une implémentation permise, mais je ne vois
pas a priori de contre-indication, ni d'avantage à avoir une autre
valeur.
C'était aussi mon idée, facile à implémenter, faite, sauf que,
comment faire end()-1 et retrouver begin()+(size()-1) ?
std::list<>::end() - 1 ?!?
Me semble bien qu'on peut décrémenter l'intérateur end, non ?
#include <list>
int main(){
std::list<int> li;
std::list<int>::iterator it;
li.push_back(0);
it= li.end();
it--;
assert( it == li.begin() );
return 0;
}
Ou ça marche juste parce que mon compilateur est gentil avec moi ?
1) Ajouter des éléments end/rend
Ajouter dans la liste et dans l'arbre des éléments "en plus",
des cellules/noeuds marqueurs de fin. Mais autant c'est faisable
pour la liste (avec des tests genre it->next->next != NULL
au lieu de it->next != NULL), autant dans l'arbre, ça
complique tous les codes.
Je ne vois pas l'intérêt.
De end(), on passe facilement à end()-1
Mais je ne vois pas l'intérêt dans le cas de std::list<> et
std::map<>.
Ben, c'est *une* solution.
Pointeur sur un noeud interne. Globalement, une list<T>, c'est
un list_node<T>* avec
class list_node<T> { T data; list_node<T> *prev, *next; }
Je ne vois toujours pas comment il est possible d'utiliser un simple
pointeur, de quelque type que ce soit, comme itérateur dans le cas
d'une std::list<>.
Quand je dis "simple pointeur", c'est que l'unique variable
membre est un poiteur, auquel on rajoute toutes les fonctions qui
vont autour (*, ->, l'arithmétique...)
Il me semblait que la seule contrainte de cet itérateur est d'être
comparé à un itérateur de ce conteneur. Un itérateur de fin qui
stockerait par exemple 0 comme valeur sentinelle me semblerait
acceptable (il suffit que le suivant du dernier noeud de la liste
vale lui-même 0). Mais, sans doute, je ne pense pas à certaines
contraintes ou garanties sur les itérateurs.
Ben, faire
it= li.end();
it--;
Mais est-ce valide ? Je croyais que oui mais je me plante
peut-être.
In article , drkm wrote:Pas un horible cast de la mort pour mettre un pointeur
sur conteneur dans une variable destinée à recevoir un pointeur
sur noeud interne.
Je ne comprend toujours pas. Pourquoi veux-tu dans ce cas effectuer
un cast depuis un pointeur sur conteneur ?
A partir de end(), il faut pouvoir retrouver le conteneur,
donc,
pointer sur quelque chose. Hors, on ne peut pas pointer sur un element
puisque les iterateurs ne doivent pas etre invalidé par la
destruction d'un élément désigné par un autre itérateur.
Sur quoi pointer donc ?Ce qui me vient directement à l'esprit serait d'initialiser le
pointeur membre de l'itérateur à une valeur sentinelle, comme 0. Je
ne sais pas s'il s'agit d'une implémentation permise, mais je ne vois
pas a priori de contre-indication, ni d'avantage à avoir une autre
valeur.
C'était aussi mon idée, facile à implémenter, faite, sauf que,
comment faire end()-1 et retrouver begin()+(size()-1) ?
std::list<>::end() - 1 ?!?
Me semble bien qu'on peut décrémenter l'intérateur end, non ?
#include <list>
int main(){
std::list<int> li;
std::list<int>::iterator it;
li.push_back(0);
it= li.end();
it--;
assert( it == li.begin() );
return 0;
}
Ou ça marche juste parce que mon compilateur est gentil avec moi ?
1) Ajouter des éléments end/rend
Ajouter dans la liste et dans l'arbre des éléments "en plus",
des cellules/noeuds marqueurs de fin. Mais autant c'est faisable
pour la liste (avec des tests genre it->next->next != NULL
au lieu de it->next != NULL), autant dans l'arbre, ça
complique tous les codes.
Je ne vois pas l'intérêt.
De end(), on passe facilement à end()-1
Mais je ne vois pas l'intérêt dans le cas de std::list<> et
std::map<>.
Ben, c'est *une* solution.
Pointeur sur un noeud interne. Globalement, une list<T>, c'est
un list_node<T>* avec
class list_node<T> { T data; list_node<T> *prev, *next; }
Je ne vois toujours pas comment il est possible d'utiliser un simple
pointeur, de quelque type que ce soit, comme itérateur dans le cas
d'une std::list<>.
Quand je dis "simple pointeur", c'est que l'unique variable
membre est un poiteur, auquel on rajoute toutes les fonctions qui
vont autour (*, ->, l'arithmétique...)
Il me semblait que la seule contrainte de cet itérateur est d'être
comparé à un itérateur de ce conteneur. Un itérateur de fin qui
stockerait par exemple 0 comme valeur sentinelle me semblerait
acceptable (il suffit que le suivant du dernier noeud de la liste
vale lui-même 0). Mais, sans doute, je ne pense pas à certaines
contraintes ou garanties sur les itérateurs.
Ben, faire
it= li.end();
it--;
Mais est-ce valide ? Je croyais que oui mais je me plante
peut-être.
Il me semblait avoir lu que les itérateurs de
std::list<> devaient être « at least forward iterators ». Je n'arrive
cependant pas à remettre la main dessus.
Il me semblait avoir lu que les itérateurs de
std::list<> devaient être « at least forward iterators ». Je n'arrive
cependant pas à remettre la main dessus.
Il me semblait avoir lu que les itérateurs de
std::list<> devaient être « at least forward iterators ». Je n'arrive
cependant pas à remettre la main dessus.
Marc Boyer writes:std::list<>::end() - 1 ?!?
Me semble bien qu'on peut décrémenter l'intérateur end, non ?
Ce qui n'est pas la même chose.
int main(){
std::list<int> li;
std::list<int>::iterator it;
li.push_back(0);
it= li.end();
it--;
assert( it == li.begin() );
return 0;
}
Ou ça marche juste parce que mon compilateur est gentil avec moi ?
Parce qu'il est gentil avec toi. Il t'a déclaré assert() ;-)
Pour la décrémentation de end(), j'aurais dit que ce n'était pas
correct. Mais après avoir jeté un coup d'oeil rapide à la norme, je
ne suis plus sûr. J'essaierai de chercher plus profondément, si
personne ne poste de référence.
J'ai jeté également un coup d'oeil rapide à la libstdc++ de GNU. Il
semble qu'il s'agisse d'une implémentation classique d'une liste
doublement chaînée avec noeud de tête bidon.
Dans une liste vide, ce noeud bidon a ses deux pointeurs pointant
vers lui-même. Sinon, son suivant est le premier élément, et son
précédent le dernier (et il est le suivant du dernier et le précédent
du premier).
Je pense que l'utilisation d'un tel noeud permet justement de devoir
considérer moins de cas limites (moins de tests pour voir si on est à
la fin ou au début).
Et cela règle de manière élégante la décrémentation de end(), si
elle est permise.
Ben, faire
it= li.end();
it--;
Mais est-ce valide ? Je croyais que oui mais je me plante
peut-être.
Je croyais que non mais je me plante peut-être.
Marc Boyer <Marc.Boyer@enseeiht.yahoo.fr.invalid> writes:
std::list<>::end() - 1 ?!?
Me semble bien qu'on peut décrémenter l'intérateur end, non ?
Ce qui n'est pas la même chose.
int main(){
std::list<int> li;
std::list<int>::iterator it;
li.push_back(0);
it= li.end();
it--;
assert( it == li.begin() );
return 0;
}
Ou ça marche juste parce que mon compilateur est gentil avec moi ?
Parce qu'il est gentil avec toi. Il t'a déclaré assert() ;-)
Pour la décrémentation de end(), j'aurais dit que ce n'était pas
correct. Mais après avoir jeté un coup d'oeil rapide à la norme, je
ne suis plus sûr. J'essaierai de chercher plus profondément, si
personne ne poste de référence.
J'ai jeté également un coup d'oeil rapide à la libstdc++ de GNU. Il
semble qu'il s'agisse d'une implémentation classique d'une liste
doublement chaînée avec noeud de tête bidon.
Dans une liste vide, ce noeud bidon a ses deux pointeurs pointant
vers lui-même. Sinon, son suivant est le premier élément, et son
précédent le dernier (et il est le suivant du dernier et le précédent
du premier).
Je pense que l'utilisation d'un tel noeud permet justement de devoir
considérer moins de cas limites (moins de tests pour voir si on est à
la fin ou au début).
Et cela règle de manière élégante la décrémentation de end(), si
elle est permise.
Ben, faire
it= li.end();
it--;
Mais est-ce valide ? Je croyais que oui mais je me plante
peut-être.
Je croyais que non mais je me plante peut-être.
Marc Boyer writes:std::list<>::end() - 1 ?!?
Me semble bien qu'on peut décrémenter l'intérateur end, non ?
Ce qui n'est pas la même chose.
int main(){
std::list<int> li;
std::list<int>::iterator it;
li.push_back(0);
it= li.end();
it--;
assert( it == li.begin() );
return 0;
}
Ou ça marche juste parce que mon compilateur est gentil avec moi ?
Parce qu'il est gentil avec toi. Il t'a déclaré assert() ;-)
Pour la décrémentation de end(), j'aurais dit que ce n'était pas
correct. Mais après avoir jeté un coup d'oeil rapide à la norme, je
ne suis plus sûr. J'essaierai de chercher plus profondément, si
personne ne poste de référence.
J'ai jeté également un coup d'oeil rapide à la libstdc++ de GNU. Il
semble qu'il s'agisse d'une implémentation classique d'une liste
doublement chaînée avec noeud de tête bidon.
Dans une liste vide, ce noeud bidon a ses deux pointeurs pointant
vers lui-même. Sinon, son suivant est le premier élément, et son
précédent le dernier (et il est le suivant du dernier et le précédent
du premier).
Je pense que l'utilisation d'un tel noeud permet justement de devoir
considérer moins de cas limites (moins de tests pour voir si on est à
la fin ou au début).
Et cela règle de manière élégante la décrémentation de end(), si
elle est permise.
Ben, faire
it= li.end();
it--;
Mais est-ce valide ? Je croyais que oui mais je me plante
peut-être.
Je croyais que non mais je me plante peut-être.
Marc Boyer writes:J'ai tenté de jetter un coup d'oeuil sur les sources
de la stl de gcc, et les itérateurs semblent n'etre qu'un
pointeur. Le list semble codé avec un element en rab, en
tête (?), end() étant l'adresse de ce pointeur, et
begin étant end()->next (en gros). Pour l'arbre, ça semble
être la racine, mais j'ai pas compris comment ils font si
par hasard on fait un erase() de cette racine...
Ce ne serait pas un element en plus egalement qui serait donc garanti
de rester toujours la racine?
Marc Boyer <Marc.Boyer@enseeiht.yahoo.fr.invalid> writes:
J'ai tenté de jetter un coup d'oeuil sur les sources
de la stl de gcc, et les itérateurs semblent n'etre qu'un
pointeur. Le list semble codé avec un element en rab, en
tête (?), end() étant l'adresse de ce pointeur, et
begin étant end()->next (en gros). Pour l'arbre, ça semble
être la racine, mais j'ai pas compris comment ils font si
par hasard on fait un erase() de cette racine...
Ce ne serait pas un element en plus egalement qui serait donc garanti
de rester toujours la racine?
Marc Boyer writes:J'ai tenté de jetter un coup d'oeuil sur les sources
de la stl de gcc, et les itérateurs semblent n'etre qu'un
pointeur. Le list semble codé avec un element en rab, en
tête (?), end() étant l'adresse de ce pointeur, et
begin étant end()->next (en gros). Pour l'arbre, ça semble
être la racine, mais j'ai pas compris comment ils font si
par hasard on fait un erase() de cette racine...
Ce ne serait pas un element en plus egalement qui serait donc garanti
de rester toujours la racine?
En fait, avec dans l'itérateur un pointeur sur l'élément *et*
un pointeur sur le conteneur, on peut avoir le passage de end()
a son prédécesseur (et inversement) en temps constant. Mais
on double la taille d'un itérateur.
Est-ce grave docteur ? ;-)
En fait, avec dans l'itérateur un pointeur sur l'élément *et*
un pointeur sur le conteneur, on peut avoir le passage de end()
a son prédécesseur (et inversement) en temps constant. Mais
on double la taille d'un itérateur.
Est-ce grave docteur ? ;-)
En fait, avec dans l'itérateur un pointeur sur l'élément *et*
un pointeur sur le conteneur, on peut avoir le passage de end()
a son prédécesseur (et inversement) en temps constant. Mais
on double la taille d'un itérateur.
Est-ce grave docteur ? ;-)