AccueilđŸ‡«đŸ‡·Chercher

Template (programmation)

En programmation informatique, les templates (en français modÚles, parfois aussi appelés patrons) sont une particularité de la programmation en langage C++, qui autorise l'écriture d'un code sans considération envers le type des données avec lesquelles il sera finalement utilisé. Les templates introduisent le concept de programmation générique dans le langage.

Les templates sont d'une grande utilité pour les programmeurs en C++, plus particuliÚrement en les combinant avec l'héritage multiple, la surcharge d'opérateur ou plus généralement la programmation orientée objet. La bibliothÚque standard de C++ fournit de nombreux outils utiles dans un cadre de travail fait avec les templates, en particulier dans la STL.

Le mécanisme des templates a aussi été inclus dans d'autres langages objet comme Java, mais a une signification différente lors de la compilation, puisqu'il s'appuie sur la super-classe Object du langage.

IntĂ©rĂȘt

Les templates permettent Ă  une classe (class, struct ou union) ou une fonction de pouvoir s'adapter Ă  plusieurs types sans avoir besoin d'ĂȘtre recopiĂ©e ou surchargĂ©e.

Les templates sont (souvent) une alternative aux macros prĂ©processeur, dĂ©conseillĂ©es en C++. Certaines d'entre elles pourront donc ĂȘtre mises Ă  niveau ; par exemple, la macro[1] :

#define MIN(a,b) (((a) < (b)) ? (a) : (b))

pourra ĂȘtre remplacĂ©e en C++ par le patron de fonction[2] :

template <typename T> 
inline T min(const T &x, const T &y)
{
    return (x < y) ? x : y;
}

De plus, en programmation orientĂ©e objet, il arrive souvent que l'on veuille Ă©crire une classe sans considĂ©rer les types, ou certaines valeurs. Avec les langages non-objet, il Ă©tait toujours possible de recopier la fonction en changeant les types lĂ  oĂč c'est nĂ©cessaire, et ce pour chaque type de base. Seulement, la POO permet l'ajout de types dĂ©finis par l'utilisateur qui ne peuvent pas ĂȘtre prĂ©vus. Les templates sont apparus pour pallier le problĂšme.

Aperçu technique au travers d'exemples

DĂ©claration d'un patron

La dĂ©claration d'une classe ou fonction gĂ©nĂ©rique doit ĂȘtre prĂ©cĂ©dĂ©e par :

template <typename T, typename U>

Le mot clĂ© class peut ĂȘtre utilisĂ© Ă  la place de typename. Les types T et U pourront alors ĂȘtre utilisĂ©s dans le corps de la classe ou de la fonction.

Avec les classes, on peut aussi se servir de la généricité pour utiliser une valeur constante :

template <typename T, int N>

La valeur entiĂšre N pourra ĂȘtre utilisĂ©e dans la classe comme s'il s'agissait d'un entier constant.

Chaque type ou valeur déclaré peut posséder une valeur par défaut, comme les arguments muets d'une fonction.

Il y a deux types de patrons.

Exemple : fonction min<T>(T,T)

Un patron de fonction pourra supporter n'importe quel type pour ses arguments. Par exemple :

template <typename T>
inline T min(const T &x, const T &y)
{
    return (x < y) ? x : y;
}

Appel de la fonction

Voici un exemple d'utilisation :

#include <iostream>
int main ()
{
    // Appel de min<int>
    std::cout << min(-20, 5) << ' ';
    // Appel de min<double>
    std::cout << min(6.4, -17.3) << ' ';
    // Appel de min<double> ; le type doit ĂȘtre prĂ©cisĂ© pour pallier l'ambiguĂŻtĂ©
    std::cout << min<double>(6.96e2, 48) << std::endl;
}

Le programme affichera la ligne suivante :

-20 -17.3 48

Le type T est dĂ©fini par les paramĂštres donnĂ©s Ă  la fonction min. Une fonction gĂ©nĂ©rique doit donc comporter un argument de chacun des types qu'elle compte utiliser pour pouvoir ĂȘtre compilĂ©e.

Cependant, si plusieurs arguments dĂ©finissent un mĂȘme type T pour la fonction mais ne sont pas du mĂȘme type, le type utilisĂ© doit ĂȘtre explicitement indiquĂ© (les arguments seront donc convertis dans ce type si nĂ©cessaire).

Dans la bibliothĂšque standard

Dans la STL, on trouve des patrons de fonction en particulier avec les algorithmes du fichier d'en-tĂȘte <algorithm>. Par exemple :

  • extrema : std::min, std::max ;
  • compter/chercher : std::count, std::find ;
  • modification : std::copy, std::fill, std::swap ;
  • tri : std::sort


Exemple : classe Tab<T,N>

Déclarer une classe comme patron lui permet en particulier de pouvoir posséder des membres d'un type défini par l'utilisateur. Par exemple[3] - [4] :

#include <cstdarg>
template <typename T, int N>
class Tab
{
    /* Membres */
        // Ici est stocké le tableau avec N éléments du type T
        T tab[N];
        // Utile pour l'affichage
        const char *separateur;
    public :
    /* Constructeurs */
        // Constructeur par défaut
        Tab<T,N> (const T &t=0) : separateur(" ")
        {
            for (int i=0; i<N; i++)
                tab[i]=t;
        }
        // Ce constructeur permet d'initialiser les éléments du tableau
        Tab<T,N> (const T &t1, const T &t2, ...) : separateur(" ")
        {
            tab[0]=t1, tab[1]=t2;
            va_list args;
            va_start (args,t2);
            for (int i=2; i<N; i++)
              tab[i]=va_arg(args,T);
            va_end (args);
        }
        // Constructeur par recopie (notez qu'il s'agit de celui par défaut)
        Tab<T,N> (const Tab<T,N> &t) : tab(t.tab), separateur(t.separateur)
        {}
        // Surdéfinition de l'opérateur d'affectation (notez qu'il s'agit de celle par défaut)
        Tab<T,N> &operator= (const Tab<T,N> &t)
        {
            for (int i=0; i<N; i++)
                tab[i]=t.tab[i];
            return *this;
        }
    /* Fonctions d'accÚs et d'altération */
        int size () const
        { return N; }
        const char *obt_sep () const
        { return separateur; }
        void config_sep (const char *nouv_sep)
        { separateur=nouv_sep; }
        const Tab<T,N> &operator() (const char *nouv_sep)
        { separateur=nouv_sep; return *this; }
        T &operator[] (int i)
        { return tab[i]; }
        const T &operator[] (int i) const
        { return tab[i]; }
        template <int N2> operator Tab<T,N2> ()
        {
        	Tab<T,N2> t;
        	for (int i=0; i<((N<N2)? N:N2); i++)
        		t.tab[i]=tab[i];
    		return t;
        }
};

La notation Tab<T,N> est ici redondante et pourrait ĂȘtre remplacĂ©e simplement par Tab.

DĂ©claration d'objets de la classe

Voici un exemple d'utilisation :

#include <iostream>
#include <algorithm>
// Surcharge de l'opérateur de décalage binaire vers la gauche
 // pour pouvoir envoyer nos tableaux sur un flot de sortie
template <typename T, int N>
std::ostream &operator<< (std::ostream &sortie, const Tab<T,N> &tab)
{
    for (int i=0; i<N; i++)
        sortie << tab[i] << ((i<N-1)? tab.obt_sep():"");
}
int main ()
{
    /* Deux listes de cinq flottants */
    Tab<double,5> liste1, liste2(66.17,4.3e3,22e5,1e-4,liste1[4]);
    liste1=liste2;
    for (int i=0; i<liste1.size(); i++)
        liste1[i]*=1.5;
    std::cout << liste1 << std::endl;
    /* Des tirets pour séparer */
    std::cout << Tab<char,37>('-')("") << std::endl;
    /* Calculs sur un tableau à deux dimensions (19x19) : création de dix carrés imbriqués */
    Tab<Tab<int,19>,19> carres;
    for (int i=0; i<=carres.size()/2; i++)
        for (int j=0; j<carres[i].size(); j++)
            carres[i][j]=std::max(9-i,std::abs(9-j));
    for (int i=0; i<carres.size(); i++)
        carres[18-i]=carres[i];
    carres.config_sep("\n");
    std::cout << carres << std::endl;
}

Ceci affichera :

99.255 6450 3.3e+06 0.00015 0
-------------------------------------
9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9
9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9
9 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 9
9 8 7 6 6 6 6 6 6 6 6 6 6 6 6 6 7 8 9
9 8 7 6 5 5 5 5 5 5 5 5 5 5 5 6 7 8 9
9 8 7 6 5 4 4 4 4 4 4 4 4 4 5 6 7 8 9
9 8 7 6 5 4 3 3 3 3 3 3 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 2 2 2 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 1 1 1 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 1 0 1 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 1 1 1 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 2 2 2 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 3 3 3 3 3 3 4 5 6 7 8 9
9 8 7 6 5 4 4 4 4 4 4 4 4 4 5 6 7 8 9
9 8 7 6 5 5 5 5 5 5 5 5 5 5 5 6 7 8 9
9 8 7 6 6 6 6 6 6 6 6 6 6 6 6 6 7 8 9
9 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 9
9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9
9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9

Les arguments du patron sont donc ici indiqués entre chevrons aprÚs le nom de la classe.

Attention lors de l'imbrication de noms de classes patron : les chevrons doivent ĂȘtre sĂ©parĂ©s par un caractĂšre d'espacement, sinon ils seront interprĂ©tĂ©s comme l'opĂ©rateur de dĂ©calage binaire et la compilation Ă©chouera. Par exemple, un vecteur de vecteurs d'entiers doit ĂȘtre dĂ©clarĂ© de cette maniĂšre :

#include <vector>
std::vector<std::vector<int> > matrix;

Ce problĂšme mineur devrait ĂȘtre rĂ©solu dans la prochaine norme du C++, C++1x.

Dans la bibliothĂšque standard

Dans la STL, on en rencontre en particulier avec les conteneurs, comme :

  • sĂ©quentiels : std::vector, std::deque et std::list ;
  • associatifs : std::map et std::set ;

mais aussi dans bien d'autres domaines, tels que :

  • les couples de variables : std::pair de <utility> ;
  • les nombreux std::basic_* (istream, ostream, string
)


Compilation des patrons

Classes patron

À chaque fois qu'un patron de classe est utilisĂ© avec de nouveaux arguments, une nouvelle classe patron (attention Ă  bien diffĂ©rencier les deux concepts) est compilĂ©e. Ainsi, la dĂ©claration d'un objet de type std::vector<int> et d'un autre de type std::vector<double> instanciera deux classes diffĂ©rentes. Les objets seront donc de types diffĂ©rents, sans qu'aucun lien puisse ĂȘtre fait entre les deux (pas mĂȘme une conversion).

Fonctions patron

Le comportement est similaire avec les fonctions : chaque appel d'un patron de fonction avec de nouveaux types d'arguments compile une nouvelle fonction patron.

Les templates dans l'Ă©dition de liens

Les patrons de classes et de fonctions doivent ĂȘtre inclus dans tous les fichiers qui les utilisent. Ils ne sont donc pas pris en compte lors de l'Ă©dition de liens, bien que seule une classe/fonction patron de chaque type soit finalement incluse dans l'exĂ©cutable.

La prochaine norme, C++1x, devrait permettre de déclarer des templates externes.

Templates variadiques

C++11 introduit les templates variadiques qui, Ă  l'image des fonctions variadiques du langage C, peuvent supporter un nombre variable de types.

Les std::tuple déjà présents dans Boost en seront l'illustration la plus basique.

Lien externe

Notes et références

  1. DĂ©finie dans de nombreuses bibliothĂšques non standard, comme <windows.h>.
  2. DĂ©fini dans <algorithm>.
  3. Ces codes sont testés.
  4. Voir fonction variadique pour une explication du mécanisme du second constructeur. Voir aussi la surcharge des opérateurs pour une explication du mécanisme d'indexation et d'affectation.
Cet article est issu de wikipedia. Text licence: CC BY-SA 4.0, Des conditions supplĂ©mentaires peuvent s’appliquer aux fichiers multimĂ©dias.