<iterator> : conception d'iterator, introduction aux traits - C++ - Programmation
Marsh Posté le 31-01-2005 à 22:06:57
à gueuler sur les débutants : "va lire tous les autres "Sujets utiles" C++"
Marsh Posté le 31-01-2005 à 22:16:57
Joce, tu veux pas agrandir cette saleté de fenêtre d'édition, j'en chie grave
Marsh Posté le 31-01-2005 à 23:15:43
j'ai pas terminé, faudra que je pas que j'oublie de boucler ça
Marsh Posté le 31-01-2005 à 23:27:18
Taz a écrit : Joce, tu veux pas agrandir cette saleté de fenêtre d'édition, j'en chie grave |
hfr enhance
Marsh Posté le 01-02-2005 à 13:24:30
voui une question
Citation : Il vous reste donc une chose à faire : quand vous créez votre classe d'iterator, héritez publiquement de std::iterator<ce qui va bien>. Ça ne coûte rien (Voir plus bas pour un exemple.) |
meuh les héritages sont privés dans l'exemple ...
Marsh Posté le 01-02-2005 à 15:26:55
Si je ne me trompe pas ils sont publics puisque ce sont des structures et pas des classes...
Marsh Posté le 01-02-2005 à 15:39:04
au temps pour moi, j'ignorai que le mot clé struct implicitait un héritage public par défaut. Allez je m'autoflagelle d'un coup de stroustrup sur le crane
Marsh Posté le 01-02-2005 à 17:02:26
ReplyMarsh Posté le 06-02-2005 à 17:26:16
un autre exemple de traits dans la bibliothèque standard du C++ : <limits>
Marsh Posté le 08-02-2005 à 00:01:59
ça intéresse personne ? c'est trop technique ? l'exemple n'est pas assez concret ? ou quoi ?
Marsh Posté le 08-02-2005 à 00:06:32
D'accord, mais bon, c'est pour présenter un concept général. Et je trouves qu'on néglige un peu les iterators : j'ai déjà donné un exexemple d'insert_iterator pour réaliser des insertions dans un dictionnaire en appliquant un petit traitement : c'est un petit paradigme.
J'ai voulu faire un peu concret, en m'appuyant sur les iterators, parler crument des traits, ça aurait encore plus bidesque
Marsh Posté le 08-02-2005 à 00:28:23
Taz a écrit : ça intéresse personne ? c'est trop technique ? l'exemple n'est pas assez concret ? ou quoi ? |
Si c'est intéressant, mais peut etre que tout les gens intéressés ont lu le BS, et l'exemple qui est à peu près le meme
En fait, je pense que c'est un peu trop concret. Ce qui serait intéressant, ce serait de généraliser la bonne utilisation des traits dans un contexte plus général. Dans quels circonstances réaliser une conception qui pourrait efficacement tirer profit de l'utilisation des traits ? Je sais pas si je suis clair ...
Citation : J'ai voulu faire un peu concret, en m'appuyant sur les iterators, parler crument des traits, ça aurait encore plus bidesque |
meuh non !
Marsh Posté le 08-02-2005 à 12:06:29
ca m'interresse beaucoup aussi, jme plonge dedans ce soir
merci beaucoup
Marsh Posté le 08-02-2005 à 13:35:53
ReplyMarsh Posté le 08-02-2005 à 13:40:48
Taz a écrit : ça intéresse personne ? c'est trop technique ? l'exemple n'est pas assez concret ? ou quoi ? |
t'inquiète pas, j'ai mis tous tes topics C++ en favoris, je m'y réfère souvent !
(récemment j'ai relu ton topic sur la spécialisation des templates, j'espérais pouvoir l'utiliser pour mon plugin Winamp mais je me suis aperçu que c'était un coup de canon pour tuer une mouche
Marsh Posté le 31-01-2005 à 21:55:28
Pré-requis :
- savoir ce qu'est un iterator
- spécialisation partielle
- mot-clef typename
Et oui, vous ne le saviez pas, mais <iterator>, c'est un entête standard. Qu'est-ce qu'il y a dedans ?
- des définitions, classes et fonctions relatives aux iterators.
- des "traits" pour iterator.
Voici une petite description de ce contenu et j'en profite pour faire une introduction aux traits et quelques rappels sur comment bien faire un iterator.
Un iterator au sens STL, doit obéir à une certaine "interface". Celle-ci est simple :
Il s'agit tout simplement de 5 petits typedef bien utiles. Notez les valeurs par défaut. À propos de Category : il s'agit d'un type parmi std::{ input_iterator_tag, output_iterator_tag, forward_iterator_tag, bidirectional_iterator_tag, random_access_iterator_tag }. C'est la matérialisation des concepts souvent employés RandomAccessIterator, ForwardIterator, etc. Libre à vous définir vos propres catégories (en héritant de std::andom_access_iterator_tag par exemple).
Il vous reste donc une chose à faire : quand vous créez votre classe d'iterator, héritez publiquement de std::iterator<ce qui va bien>. Ça ne coûte rien (Voir plus bas pour un exemple.)
C'est quoi les traits
Les iterators sont une ~abstraction des pointeurs. En C++, ils servent à matérialiser le concept de séquence. Dans une application, on a souvent besoin de mélanger iterators et pointeurs classiques. Le problème, c'est que vous coder un algorithme générique, mais les petites différences entre pointeurs et iterators sont assez ennuyeuses à gérer. Et bien les traits sont une solution à ce genre de problème.
Think of a trait as a small object whose main purpose is to carry information used by another object or algorithm to determine "policy" or "implementation details". B.S.
Les traits sont des petits objets qui fournissent des informations à propos d'un autre objet (ou algorithme) pour déterminer la marche à suivre ou des détails d'implémentation. Dans notre cas, nous avons donc besoin d'une classe iterator_traits pour faire la jointure entre pointeur et iterator. Techniquement, les traits se présentent comme une cartographie de type : on fournit un argument template T, et le traits va nous renseigner grâce à une série de typedef.
Description de std::iterator_traits<>
Rien de très compliqué, on veut juste unifier iterators, pointeurs et const-pointeurs. On fournit 5 typedefs, c'est la projection des typedefs de std::iterator.
Notez bien les différences sur "pointer" et "reference".
L'exemple
Objectifs :
- définir une fonction distance(first, last) qui calcule la distance entre 2 itérateurs. Cette fonction est semblable à std:: distance définie dans <iterator>
- améliorer cette fonction si nécessaire.
Pour la première partie, c'est relativement facile. Une fois qu'on a passé les longs noms de types, c'est de la tarte. Pas de quoi s'affoler. Ici, on utilise les traits pour obtenir "automatiquement" le type de retour adéquate représentant une différence entre 2 iterator.
Maintenant, bricolons-nous 2 classes d'iterator pour voir ce que ça donne :
- un ForwardIterator (typiquement un iterator de liste)
- un RandomAccessIterator (un iterator de vecteur)
sont 2 classes simples qui porte chacune une valeur.
NB : j'ai "tassé" tout le code dans définition pour faire au plus cours possible, et je n'ai défini que les fonctions membres nécessaires à l'exemple. Par exemple pour la classe Forward, il manque un operator++(int).
Et voilà un main de test. On utilise des int comme argument pour Forward et Random.
Très bien ça marche. Maintenant, c'est un peu dommage que le calcul de distance entre 2 RandomAccessIterator soit en temps linéaire. Ça veut dire que si notre iterator est sur un conteneur à accès aléatoire en temps constant, et bien, on perdrait ce bénéfice : on se retrouve à faire le même traitement que si on avait une liste. On parcours élément par élément et on compte. C'est une perte de temps, et c'est bien compliqué. Nous sommes tous relativement habitués à l'arithmétique des pointeurs : c'est quand même plus pratique (et rapide) de pouvoir faire 'p += n' que 'n' fois '++'. Je rappelle qu'un RandomAccessOperator permet des opérations telles que += et -= donc par extension + et -. Il serait donc inutile de faire n iéerations pour obtenir la distance.
std::iterator_traits<> nous permet d'obtenir la catégorie d'un type d'iterator. On va donc exploiter cette information pour prendre une décision lors du calcul de distance.
C'est gagné !
Il va de soit qu'une fois que le compilateur est passé la dessus, à grand coup d'inlining, on tombe sur une expression simple. Par exemple, si R est un RandomAccessIterator, My:: distance2(R(M), R(N)) sera réduit -- à la compilation -- à l'expression (M - N). Et oui, tout ça pour ça Si vous trouvez que ça ne vaut pas la peine, la version générique My:: distance vous satisfera. Remarquez juste que si un jour, vous décidez que changez d'algorithme dans un cas particulier (ici, passage d'une boucle à une sous-traction), une simple recompilation suffira.
----
Le programme complet
Message édité par Taz le 06-02-2005 à 16:03:43