La définition des fonctions et des classes template ne génère aucun code tant que tous les paramè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 valeur 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'instanciation explicite.
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 paramètres template à utiliser. Si aucune ambiguïté n'a lieu, il génère le code pour ce jeu de paramè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.
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 template Min
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 template Min
ne peut pas être instanciée
dans le code suivant :
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'expression qui exige une instanciation implicite. Par exemple, la définition d'un objet d'une classe template 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 template 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; }
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.
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 se 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 :
Cette syntaxe peut être simplifiée pour les fonctions
template, à condition que tous les paramètres template puissent
être déduits par le compilateur des types des paramètres utilisés dans la déclaration de la fonction.
Ainsi, il est possible de forcer l'instanciation de la fonction template
Min
de la manière suivante :
Dans cet exemple, la fonction template
Min
est instanciée pour le type int, puisque ses paramètres sont
de ce type.
Lorsqu'une fonction ou une classe template a des valeurs par défaut pour ses paramètres template, 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 peut être vide (mais les signes d'infériorité et de supériorité doivent malgré tout être présents).
Les template doivent impérativement être définis lors de leur instanciation pour que le compilateur 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 inconvé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éclaration de la définition dans des fichiers séparés. Le deuxième inconvénient est que les instances des template sont compilées plusieurs fois, ce qui diminue d'autant plus les performances des compilateurs. Enfin, ce qui est le plus grave, c'est que les instances des template sont en multiples exemplaires 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 considérés comme des 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 ennuyant dans le cas de bibliothèques template écrites et vendues par des sociétés désireuses de conserver leur savoir-faire. Pour résoudre ce problème, le langage donne la possibilité d'exporter les définitions 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 technique 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 traitements 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 regroupe les différentes instances des mêmes 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 stocké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 template peuvent n'être définies que dans un seul fichier source, réservé à cet effet. Cette dernière solution est de loin la plus sûre, et il est donc recommandé d'écrire un tel fichier pour chaque programme.
Ce paragraphe vous a présenté trois des principaux problèmes soulevés par l'utilisation des template, ainsi que les solutions les plus courantes qui y ont été apportées. Il est vivement recommandé 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.
Précédent | Sommaire | Suivant |
Fonctions et classes template | Niveau supérieur | Spécialisation des template |