• Aucun résultat trouvé

I. Le langage C++

12. Les template

12.4. Instanciation des template

La définition des fonctions et des classes template ne génère aucun code tant que tous les para- mètres template n'ont pas pris chacun une valeur spécifique. Il faut donc, lors de l'utilisation d'une fonction ou d'une classe template, fournir les valeurs pour tous les paramètres qui n'ont pas de va- leur par défaut. Lorsque suffisamment de valeurs sont données, le code est généré pour ce jeu de valeurs. On appelle cette opération l'instanciation des template.

Plusieurs possibilités sont offertes pour parvenir à ce résultat : l'instanciation implicite et l'instan-

ciation explicite.

12.4.1. Instanciation implicite

L'instanciation implicite est utilisée par le compilateur lorsqu'il rencontre une expression qui utilise pour la première fois une fonction ou une classe template, et qu'il doit l'instancier pour continuer son travail. Le compilateur se base alors sur le contexte courant pour déterminer les types des para- mètres template à utiliser. Si aucune ambiguïté n'a lieu, il génère le code pour ce jeu de para- mètres.

La détermination des types des paramètres template peut se faire simplement, ou être déduite de l'expression à compiler. Par exemple, les fonctions membres template sont instanciées en fonction du type de leurs paramètres. Si l'on reprend l'exemple de la fonction template Min définie dans l'Exemple 12-4, c'est son utilisation directe qui provoque une instanciation implicite.

Exemple 12-10. Instanciation implicite de fonction template

int i=Min(2,3);

Dans cet exemple, la fonction Min est appelée avec les paramètres 2 et 3. Comme ces entiers sont tous les deux de type int, la fonction templateMin est instanciée pour le type int. Partout dans la définition de Min, le type générique T est donc remplacé par le type int.

Si l'on appelle une fonction template avec un jeu de paramètres qui provoque une ambiguïté, le compilateur signale une erreur. Cette erreur peut être levée en surchargeant la fonction template

par une fonction qui accepte les mêmes paramètres. Par example, la fonction templateMin ne peut pas être instanciée dans le code suivant :

int i=Min(2,3.0);

parce que le compilateur ne peut pas déterminer si le type générique T doit prendre la valeur int ou double. Il y a donc une erreur, sauf si une fonction Min(int, double) est définie quelque part. Pour résoudre ce type de problème, on devra spécifier manuellement les paramètres template de la fonction, lors de l'appel. Ainsi, la ligne précédente compile si on la réécrit comme suit :

int i=Min<int>(2,3.0);

dans cet exemple, le paramètre template est forcé à int, et 3.0 est converti en entier.

On prendra garde au fait que le compilateur utilise une politique minimaliste pour l'instanciation implicite des template. Cela signifie qu'il ne créera que le code nécessaire pour compiler l'expres- sion qui exige une instanciation implicite. Par exemple, la définition d'un objet d'une classe tem- plate dont tous les types définis provoque l'instanciation de cette classe, mais la définition d'un pointeur sur cette classe ne le fait pas. L'instanciation aura lieu lorsqu'un déréférencement sera fait

par l'intermédiaire de ce pointeur. De même, seules les fonctionnalités utilisées de la classe tem- plate seront effectivement définies dans le programme final.

Par exemple, dans le programme suivant :

#include <iostream> using namespace std; template <class T> class A { public: void f(void); void g(void); };

// Définition de la méthode A<T>::f() : template <class T>

void A<T>::f(void) {

cout << "A<T>::f() appelée" << endl; }

// On ne définit pas la méthode A<T>::g()... int main(void)

{

A<char> a; // Instanciation de A<char>. a.f(); // Instanciation de A<char>::f(). return 0;

}

seule la méthode f de la classe template A est instanciée, car c'est la seule méthode utilisée à cet endroit. Ce programme pourra donc parfaitement être compilé, même si la méthode g n'a pas été dé- finie.

12.4.2. Instanciation explicite

L'instanciation explicite des template est une technique permettant au programmeur de forcer l'instanciation des template dans son programme. Pour réaliser une instanciation explicite, il faut spécifier explicitement tous les paramètres template à utiliser. Cela ce fait simplement en donnant la déclaration du template, précédée par le mot-clé template :

template nom<valeur[, valeur[...]]>

Par exemple, pour forcer l'instanciation d'une pile telle que celle définie dans l'Exemple 12-5, il faudra préciser le type des éléments entre crochets après le nom de la classe :

template Stack<int>; // Instancie la classe Stack<int>.

Cette syntaxe peut être simplifiée pour les fonctions template, à condition que tous les para- mètres template puissent être déduits par le compilateur des types des paramètres utilisés dans la

Chapitre 12. Les template

déclaration de la fonction. Ainsi, il est possible de forcer l'instanciation de la fonction template Min de la manière suivante :

template int Min(int, int);

Dans cet exemple, la fonction templateMin est instanciée pour le type int, puisque ses para- mètres sont de ce type.

Lorsqu'une fonction ou une classe template a des valeurs par défaut pour ses paramètres tem- plate, il n'est pas nécessaire de donner une valeur pour ces paramètres. Si toutes les valeurs par défaut sont utilisées, la liste des valeurs est vide (mais les signes d'infériorité et de supériorité doi- vent malgré tout être présents).

Exemple 12-11. Instanciation explicite de classe template

template<class T = char> class Chaine;

template Chaine<>; // Instanciation explicite de Chaine<char>.

12.4.3. Problèmes soulevés par l'instanciation des template

Les template doivent impérativement être définis lors de leur instanciation, pour que le compila- teur puisse générer le code de l'instance. Cela signifie que les fichiers d'en-tête doivent contenir non seulement la déclaration, mais également la définition complète des template. Cela a plusieurs in- convénients. Le premier est bien entendu que l'on ne peut pas considérer les template comme les fonctions et les classes normales du langage, pour lesquels il est possible de séparer la définition de la déclaration dans des fichiers séparés. Le deuxième inconvénient est que les instances des tem- plate sont compilées plusieurs fois, ce qui diminue d'autant plus les performances des compila- teurs. Enfin, ce qui est le plus grave, c'est que les instances des template sont en multiples exem- plaires dans les fichiers objets générés par le compilateur, et accroissent donc la taille des fichiers exécutables à l'issue de l'édition de liens. Cela n'est pas gênant pour les petits programmes, mais peut devenir rédhibitoire pour les programmes assez gros.

Le premier problème n'est pas trop gênant, car il réduit le nombre de fichiers sources, ce qui n'est en général pas une mauvaise chose. Notez également que les template ne peuvent pas être consi- dérés comme les fichiers sources classiques, puisque sans instanciation, ils ne génèrent aucun code machine (ce sont des classes de classes, ou “ métaclasses ”). Mais ce problème peut devenir en- nuyant dans le cas de librairies template écrites et vendues par des sociétés désireuses de conser- ver leur savoir-faire. Pour résoudre ce problème, le langage donne la possibilité d'exporter les défi- nitions des template dans des fichiers complémentaires. Nous verrons la manière de procéder dans la Section 12.7.

Le deuxième problème peut être résolu avec l'exportation des template, ou par tout autre tech- nique d'optimisation des compilateurs. Actuellement, la plupart des compilateurs sont capables de générer des fichiers d'en-tête précompilés, qui contiennent le résultat de l'analyse des fichiers d'en-tête déjà lus. Cette technique permet de diminuer considérablement les temps de compilation, mais nécessite souvent d'utiliser toujours le même fichier d'en-tête au début des fichiers sources. Le troisième problème est en général résolu par des techniques variées, qui nécessitent des traite- ments complexes dans l'éditeur de liens ou le compilateur. La technique la plus simple, utilisée par la plupart des compilateurs actuels, passe par une modification de l'éditeur de liens, pour qu'il re- groupe les différentes instances des même template. D'autres compilateurs, plus rares, gèrent une base de données dans laquelle les instances de template générées lors de la compilation sont stoc-

kées. Lors de l'édition de liens, les instances de cette base sont ajoutées à la ligne de commande de l'éditeur de liens, afin de résoudre les symboles non définis. Enfin, certains compilateurs permettent de désactiver les instanciations implicites des template. Cela permet de laisser au programmeur la responsabilité de les instancier manuellement, à l'aide d'instanciations explicites. Ainsi, les tem- plate peuvent n'être définies que dans un seul fichier source, réservé à cet effet. Cette dernière so- lution est de loin la plus sûre, et il est donc recommandé d'écrire un tel fichier pour chaque pro- gramme.

Ce paragraphe vous a présenté trois des principaux problèmes soulevés par l'utilisation des tem- plate, ainsi que les solutions les plus courantes qui y ont été apportées. Il est vivement recomman- dé de consulter la documentation fournie avec l'environnement de développement utilisé, afin à la fois de réduire les temps de compilation et d'optimiser les exécutables générés.