Précompilation de templates
Pré-compiler un template serait un avantage pour accélérer les temps de compilation des projets, mais cela
semble actuellement difficile à réaliser. Il existe pourtant une méthode complètement standard qui permet
d'approcher cet idéal.
Je commence par rappeler rapidement le principe d'utilisation habituel des templates, puis j'explique comment organiser le code pour pouvoir faire de la précompilation.
Un template doit normalement être défini dans le fichier d'en-tête où il est déclaré.Rappel:
À part dans le cas d'un petit template utilitaire défini dans un fichier *.cpp pour un usage restreint, il est plus courant de déclarer les templates dans un fichier d'en-tête. Dans ce cas, il est obligatoire d'écrire le code dans ce même fichier d'en-tête, sinon le compilateur ne pourra pas réaliser l'instanciation avec l'argument template requis. Voyez l'exemple suivant:
Que s'est-il passé ? Le compilateur ne peut pas réellement écrire un code générique pour le template. Au contraire, quand il a besoin d'une version spécifique de la fonction, (ici avec T=int), il la réecrit intégralement en remplaçant T par sa valeur (ici int). A la limite, si un template n'est jamais instancié, il pourrait contenir des erreurs.
Normalement, un template doit être inlineUn template, que ce soit une fonction ou une classe, doit généralement être déclaré inline.Prenons l'exemple suivant, qui semble ne pas poser de problèmes : la fonction template "max" est utilisée dans plusieurs fichiers source
En revanche, mettre des <int> à la place des <float> dans la fonction g pose problème. Cela peut paraître surprenant mais c'est pourtant tout-à-fait logique.
Que s'est-il passé ? Dans le premier cas, l'instanciation du template a été faite pour deux types différents, tantôt int, tantôt float. La phase de pé-processing chargée d'instancier les templates nécessaires a donc généré deux fonctions "max", une version int max(int,int) et une version float max(float,float). Ces deux versions n'ont pas la même signature, ce qui signifie que le compilateur sait les différencier. En revanche, dans le cas n°2, la fonction int max(int,int) existe deux fois, et le compilateur ne peut pas deviner que le code est le même, puisqu'il a été; généré dans deux fichiers indépendants (toto1.cpp et toto2.cpp). La compilation séparée est donc correcte, mais l'édition de liens problématique. D'où l'erreur "symbole multiple". Pour résoudre le problème, la solution est de déclarer le template inline. "max" n'est alors plus une fonction mais une sorte de macro, remplacée par le bloc de code qui la définit. Dans ce cas, il n'y a pas de lien à établir lors de l'édition de lien, et tout rentre dans l'ordre. Petit rappel sur sur la signature des fonctions:Un compilateur C++ refuse de compiler s'il détecte deux fonctions de même nom et de même signature. Il dit qu'il y a ambiguïté. La signature d'une fonction correspond en fait au prototype : nombre d'arguments, type d'arguments, attributs divers (static, const...) En C, le problème ne se pose pas, puisque la surcharge de fonction n'y est pas permise. (Deux fonctions ne peuvent avoir le même nom). En revanche, en C++, il faut tenir compte du contexte pour comprendre à quelle fonction portant ce nom on fait appel. Parfois, le contexte est insuffisant et le compilateur s'arrête sur une erreur de type "ambiguïté.Comment pré-compiler un template:Nous venons de voir que l'utilisation des templates impose habituellement non seulement d'écrire du code (parfois beaucoup) dans le fichier d'en-tête, ce qui alourdit considérablement les temps de compilation, mais en plus nécessite l'inlining de ce code, ce qui peut augmenter énormément la taille des exécutables générés.Les compilateurs apportent plus ou moins de solutions à ces problèmes:
Dans cet exemple, main.cpp n'a besoin de connaître que la déclaration du template. En effet, comme on savait que seule la version <float> était nécessaire, on a forcé son instanciation dans utilitaires.cpp. Ainsi, l'édition de liens sait résoudre l'appel à float max(float,float) dans le main. Les avantages de cette technique sont immédiats:
Évidemment, ce tour de passe-passe se paye par une limitation inévitable: comme il faut forcer l'instanciation de template pour tous les types dont on aura besoin, il faut savoir à l'avance quels sont ces types, ce qui prive l'utilisateur de ce template pour tous les types qu'il aura définit lui-même, et qui aurait pu y être appliqués. On ne peut donc pas appliquer cette technique pour toutes les situations, notamment si l'on crée un conteneur template, cas où justement il aurait été appréciable de pouvoir précompiler le code. Par contre, cette technique s'avère au contraire idéale si vous devez créer une bibliothèque template et que vous voulez empêcher l'utilisateur de faire n'importe quoi en appelant les fonctions pour des types non autorisés. Enfin, pensez qu'il est possible de mixer les deux approches pour contenter des utilisateurs lambdas et des utilisateurs avancés. Pour cela, inversez les rôles des fichiers *.h et *.hxx. L'utilisateur de base pensera à inclure le fichier *.h et ne bénéficiera d'aucun changement par rapport à d'habitude; quant à l'utilisateur avancé, s'il est avisé de l'existence du couple de pré-compilation (*.hxx, *.cpp), il pourra les utiliser à sa guise. | ||||||||||||||||||||||||||||||||||||||||