Introduction à la programmation Générique avec STL

Ce cours de DEA est assez dense et n'a pas été relu il est donc reservé a un public attentif, circonspect et ayant une compétence raisonnable en C++.
 

  1. Templates

    1. les fonctions template

    2. le type ne répond pas au cahier des charges du template

      1. spécialisation d'un template

    3. Les types templates dit types paramètres

  2. STL

    1. trois interfaces de Base

    2. les containers

    3. les itérateurs

    4. les algorithmes

      1. objets fonctions

  3. ASTL

    1. Pourquoi

    2. structure de la librairie

    3. un exemple

Vous trouverez cette page a l'adresse  http://www-igm.univ-mlv.fr/~dr/COURS_STL/index.html


les fonctions  parametrées

Pour plus de détails voir le cours de C++
et plus spécialement l'écriture de code avec des  templates

La fonction suivante est générique, elle est independante du type traité:

   Résolution par le compilateur du type:

     Il est ainsi possible d'écrire

Le compilateur va construire les deux fonctions min adaptées aux besoins, c'est-à-dire  int min(int,int) et float min(float,float) et ceci automatiquement, ceci de façon invisible pour l'utilisateur. (d'ailleurs la plupart des compilateurs de C++ on déjà intégré un certain nombre de fonctions template  comme min et max à la place des macros.)
 

Le type ne répond pas au cahier des charges

Les types non comparables :

  1. ceux pour qui l'opérateur < n'est pas défini
        -> le compilateur proteste
        -> il faut alors définir l'opérateur
            de préférence a l'extérieur de la  classe:

      class MaClass {
      /* données */
      public:
      friend bool operator < (MaClass & a, MaClass & b );
      /* méthodes */
      };

      bool operator < (MaClass & a, MaClass & b )
      {
      // code de comparaison
      }

  2.  ceux pour qui la définition de l'opérateur < est incorrecte la surcharge :
     par exemple le type (char *)

 
    la fonction min nous retourne la chaîne la plus basse dans la mémoire car elle compare les pointeurs !
    La surcharge du C++ résout le problème d'appel,
    il suffit de redéfinir une fonction qui spécialise le template pour le type.
 

Spécialisation d'un template pour un type donné

    En effet cette fonction sera sélectionnée avant le template.

de même pour unsigned char.
Remarque syntaxique: unsigned différent de  signed, const T différent de T
 

Remarque syntaxique: attention à la signature dans laquelle ne rentre pas le type de la valeur de retour.
 
 

Les types templates

Deuxième outil syntaxique: les types paramètrés par des types.
Il est en effet difficile de travailler uniquement avec des fonctions paramètrées par des types selon la fameuse formule de DijkStra :
 Data Structures + algorithmes = Programmes.

Un exemple de classe template:

Quelleque variations sur le theme:

// au lieu d'une constante
template <class T,int PAS=100>
class dynaArray { /* idem */ };
// attention de ne pas multiplier les class dynaArray
// pour le même type T avec des PAS différents
// cela entraîne une explosion de la taille du code.
 
 

On peut voir sur cet exemple que oui, c'est agréable, mais que c'est vite la jungle, si tout le monde se met à écrire des template de classes formidables on ne pourra toujours pas parler de programmation générique car non réutilisable.
 
 

D'ou la définition d'une librairie standard de templates : stl qui est dans la norme actuelle du C++.


 Standard template librairie

Pour que cette librairie soit standard elle doit fournir les algorithmes de base, c'est-a-dire les structures de données les plus classiques avec les algorithmes de manipulation celles-ci.

Pour cela un certain nombre d'éléments de conception sont interessants :
 

  1. il faut que ces structures de donnée fonctionnent pour les types prédéfinis et pour les types utilisateurs.

  2. il faut que l'on ne perde pas trop en vitesse par rapport a une implémentation directe en C, (Kerningham a dit du C qu'il etait suffisament efficace pour qu'on n'écrive plus en assembleur).

  3. il faut pouvoir faire abstraction de l'implémentation, pour simplifier l'utilisation et pour permettre le choix de celle-ci !

  4. il faut garder le contrôle de type même dans l'écriture générique.

Pour répondre à ces différentes exigences Stepanov et Lee ont défini une interface à leur librairie décomposée en 3 éléments.

 

 
 

les containers

les containers sont une abstraction de stockage qui est materialisée sous plusieurs formes :
- containers séquentiels listes,deques, vecteur, (stack and queues)
- containers associatifs  map(key,value), sets, multisets(bags), multimap(key,valueS),

par exemple vous pouvez regarder le container vector.h

voici une version expurgée de l'interface de vector
 
 

Cette interface genereuse permet de manipuler le vecteur.
L'interet de cette forme de vecteur c'est qu'il stocke (alloue de la memoire) les données.
 
 

 Les iterateurs

    Les itérateurs ont pour objectif d'encapsuler la notion de pointeurs de tableaux  afin de pouvoir manipuler indifféremment des tableaux du C et des containers.  C'est une idée forte car elle permet de réutiliser la compétence acquise sur les pointeurs par de nombreux programmeurs de C et C++. Ainsi on va pouvoir écrire la bonne vieille formule pour strcpy avec des containers de n'importe quoi stockés n'importe comment :
 

et voilà si A et B sont des containers alors on peut écrire :
 

la complexité de l'opération est à priori linéaire sauf si vos containers n'ont pas un comportement standard.
 
 

Ainsi l'algorithme de recherche suivant :
 

qui semble etre ecrit pour des tableaux du C s'utilise de la facon suivante :
 

nous donne bien

Regardons maintenant la déclaration de plusieurs algorithmes
 

L'algorithme de tri implémenté dans sort utilise l'accès direct dans le container, il ne fonctionne donc que sur les containers qui ont ce type d'itérateurs ce qui est vrai pour les tableaux mais faux pour les listes (c'est possible mais très couteux).

D'ou le besoin de faire une typologie des itérateurs pour ce qu'il sont capable de fournir comme interface de déplacement :
RandomAccessIterator acces direct   it = it + n;  it = it - n;
BidirectionalIterator possibilite de faire it++; et it--;
ForwardIterator uniquement it++;
InputIterator iterateur de lecture
OutputIterator iterateur d'ecriture
Utilisation générale
les opérateurs ont pour interface

l'opérateur * qui permet de manipuler la valeur pointée.
Ce que nous avons utilisé dans le code de find.

d'autre part les opérateurs < et == sont définis (remarque).

attention avec les input itérateurs, la valeur ne peut être affectée (penser à un fichier en lecture seule), dans les ouput itérateurs il faut faire une affectation

pour chaque incrémentation

Nous avons une forme d'héritage par compatibilité des interfaces qui est définie par le graphe d'héritage suivant:

 

Les Algorithmes

    Les algorithmes de STL sont  peu nombreux mais déjà pléthoriques.
    une sélection rapide dans la page algorithmes
 

    Leur utilisation est l'objectif premier de STL, c'est pour les obtenir que l'ensemble de la librairie a été définie.
    Chercher dans #include <algo.h> l'algo à votre goût.

    Quelques remarques sur les paramètres d'appels
 

les objets fonctions

Les objets fonctions sont des objets pour qui l'opérateur () est défini.
 

on peut maintenant utiliser

qui va placer dans le container a les sommes des éléments de a avec ceux de b
voilà, l'addition de vecteurs est terminée.

Plus simple:



suite