====== Les itérateurs en C++ ======
===== Généraliser les algorithmes =====
Considérons un algorithme de recherche d'un plus grand élément :
template
T find_greatest_element(T theArray[], size_t theNumberOfElements)
{
if (theNumberOfElements == 0)
return T();
T greatestElement = theArray[0];
for (int index = 1; index < theNummberOfElements; index++)
{
if (theArray[index] > greatestElement)
greatestElement = theArray[index];
}
return greatestElement;
}
Ce code ne fonctionne qu'avec des tableaux, cependant ce code pourrait fonctionner a priori pour tout ensemble de données quelqu'il soit. En effet il suffirait de le réécrire cette manière en pseudo-code :
find_greatest_element(aCollection)
si aCollection est vide:
aucun plus grand élément.
greatestElement <= premier élément de aCollection.
tant qu'il existe un élément suivant de aCollection:
nextElement <= élément suivant de aCollection
si nextElement > greatestElement:
greatestElement = nextElement;
retourne greatestElement.
On constate donc que le code peut-être généraliser pour fonctionner avec n'importe quelle collection si on peut "énumérer" les éléments dans la cette collection d'éléments.
L'idée consiste donc à fournir une abstraction permettant d'énumérer les éléments dans une structure de données quelque soit.
===== L'itérateur : un moyen d'énumérer des valeurs dans une structure de données =====
Pour pouvoir énumérer les éléments stockés dans une structure de données, il est nécessaire que cette structure de données fournissent une classe qui va permettre d'énumérer les données. Cette classe que l'on appeller iterateur va devoir fournir les opérateurs suivants :
|''operator ==(iterator)''|test si deux iterateurs font référence au même élément stocké dans la structure de données.|
|''value_type operator *()''|retourne une référence sur l'élément stocké dans la structure de données.|
|''operator +()''|passe à l'élément situé après l'élément actuellement référence par l'itérateur.|
Il s'agit là des opérations minimales qu'un itérateur doit fournir. Il existe des énumérateurs plus riches qui ajoutent des opérations supportées comme notamment :
|''operator --()''|passe à l'élément situé avant l'élément actuellement référence par l'itérateur.|
pour des itérateurs qui sont dits réversibles.
Désormais, il est possible de réécrire le code précédent comme suit :
template
iterator find_greatest_element(iterator itStart, iterator itEnd)
{
if (itBegin == itEnd)
return itEnd;
iterator itGreatest = itStart;
itStart ++;
while(itStart != itEnd)
{
if(*itStart > *itGreatest)
itGreatest = itStart;
itStart ++;
}
return itGreatest;
}
La fonction prend en entrée un interval qui est défini par l'itérateur référençant le premier élément de la séquence et l'itérateur référençant le premier élément n'étant plus dans la séquence, celui situé après le dernier élément de la séquence (souvent appelé l'itérateur de fin). Le séquence est vide si l'itérateur ''itStart'' correspond à l'itérateur de fin ''itEnd''. Dans ce cas, nous retournons l'itérateur de fin ''itEnd'' indiquant qu'aucun élément n'a été trouvé. Sinon, nous considérons que le premier élément de la séquence est l'élément le plus grand, c'est pour cela que nous affectons à ''itGreatest'' la valeur ''itStart''. Puis nous parcourons la séquence d'élément en élément en "incrémentant" l'itérateur, incrémenter l'itérateur consiste à passer à l'élément suivant. Nous vérifions que l'élément auquel fait référence l'itérateur ''itStart'' est bien plus petit que l'élément auquel fait référence l'itérateur ''itGreatest''. Si ce n'est le cas, nous mettons à jour ''itGreatest'' avec la valeur d'''itStart'' qui correspond à l'élément courant et nous continuons d'énumérer les éléments de la séquence jusqu'à arriver au dernier élément de la séquence. La fonction retourne alors l'itérateur ''itGreatest'' qui fait référence au plus grand élément de la séquence d'éléments.
L'intérêt des itérateurs est de permettre d'énumérer des éléments stockées dans une structure de données sans avoir à connaitre ni la structure de données, ni l'implantation. C'est à la structure de données de fournir un objet ''iterator'' qui permettre d'accéder aux données selon l'interface.
De fait, il devient possible d'écrire des algorithmes qui sont indépendant des structures de données. C'est d'ailleurs le cas de la plupart des fonctions qui sont fournies par la STL dans le module [[https://en.cppreference.com/w/cpp/header/algorithm|'''']] fournissant des fonctions comme par exemple :
template< class inputIterator, class T >
inputIterator find( inputIterator first, inputIterator last, const T& value );
qui recherche l'élément ''value'' dans une séquence délimitée par deux itérateurs ''inputIterator'' ''first'' et ''last''.
Certains algorithmes de [[https://en.cppreference.com/w/cpp/header/algorithm|'''']] nécessitent des opérateurs supportant des opérations complémentaires comme par exemple
template< class randomIterator>
void sort(randomIterator first, randomIterator last);
qui classe l'ensemble des éléments situés dans la séquence délimitée par deux itérateurs ''first'' et ''last''. Cependant ces itérateurs ne sont pas des itérateurs simples que l'on qualifie [[https://en.cppreference.com/w/cpp/named_req/InputIterator|d'input itérator]] mais des itérateurs supportant des accès par indexation que l'on qualifie [[https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator|de random iterator]].
===== Les différents types d'itérateur =====
C++ introduit différentes types d'itérateur qui sont rapidement énumérés ici :
|[[https://en.cppreference.com/w/cpp/named_req/InputIterator|''input iterator'']] | itérateur permettant d'accéder en lecture à une donnée, supportant l'opération de comparaison et enfin le passage à l'élément suivant.|
|[[https://en.cppreference.com/w/cpp/named_req/OutputIterator|''input iterator'']] | itérateur permettant d'écrire des données par exemple dans un flux supportant le passage à l'élément suivant.|
|[[https://en.cppreference.com/w/cpp/named_req/ForwardIterator|''forward iterator'']] | ajoute à l'itérateur ''input operator'' la garantie que si ''a'' et ''b'' sont deux itérateurs de même type, si ''a == b'' alors ''++a == ++b''.|
|[[https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator|''bidirectional iterator'']] | ajoute à l'itérateur ''forward iterator'' la possibilité de revenir à l'élément précédent ((''operator --()'').|
|[[https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator|''random access iterator'']] | ajoute à l'itérateur ''bidirectional iterator'' la possibilité de se déplacer par saut de x éléments en avant ou en arrière.|
Un algorithme peut ne fonctionner qu'avec un type particulier d'itérateur. Ainsi un algorithme de tri comme [[https://gist.github.com/svdamani/dc57e4d1b00342d4507d|quicksort ou merge sort]] auront le plus souvent besoin que l'on leur fournissent un ''random access iterator''.