Cela s'explique par la nécessité d'une compilation séparée et par le fait que les modèles sont un polymorphisme de type instanciation.
Rapprochons-nous un peu du concret pour trouver une explication. Disons que j'ai les fichiers suivants :
- foo.h
- déclare l'interface de
class MyClass<T>
- foo.cpp
- définit la mise en œuvre de
class MyClass<T>
- bar.cpp
La compilation séparée signifie que je devrais pouvoir compiler foo.cpp indépendamment de bar.cpp . Le compilateur effectue tout le travail d'analyse, d'optimisation et de génération de code sur chaque unité de compilation de manière totalement indépendante ; nous n'avons pas besoin d'effectuer une analyse de l'ensemble du programme. Seul l'éditeur de liens doit traiter l'ensemble du programme en une seule fois, et son travail est nettement plus facile.
bar.cpp n'a même pas besoin d'exister lorsque je compile foo.cpp mais je devrais toujours être en mesure d'établir un lien entre le foo.o Je l'avais déjà fait avec le bar.o Je viens juste de produire, sans avoir besoin de recompiler foo.cpp . foo.cpp pourrait même être compilée dans une bibliothèque dynamique, distribuée ailleurs sans que l'on ait besoin de l'aide d'un tiers. foo.cpp et lié au code qu'ils écrivent des années après que j'ai écrit foo.cpp .
Le "polymorphisme de type instanciation" signifie que le modèle MyClass<T>
n'est pas vraiment une classe générique qui peut être compilée en un code fonctionnant pour n'importe quelle valeur de T
. Cela ajouterait des frais généraux tels que la mise en boîte, la nécessité de passer des pointeurs de fonction aux allocateurs et aux constructeurs, etc. L'objectif des modèles C++ est d'éviter d'avoir à rédiger des fichiers class MyClass_int
, class MyClass_float
etc., mais pour pouvoir obtenir un code compilé qui soit pratiquement identique à celui que nous aurions obtenu si nous l'avions compilé. avait écrit chaque version séparément. Un modèle est donc littéralement un modèle ; un modèle de classe est no une classe, c'est une recette pour créer une nouvelle classe pour chaque T
que nous rencontrons. Un modèle ne peut pas être compilé en code, seul le résultat de l'instanciation du modèle peut être compilé.
Ainsi, lorsque foo.cpp est compilé, le compilateur ne peut pas voir bar.cpp de savoir que MyClass<int>
est nécessaire. Il peut voir le modèle MyClass<T>
mais il ne peut pas émettre de code pour cela (c'est un modèle, pas une classe). Et quand bar.cpp est compilé, le compilateur peut voir qu'il a besoin de créer un fichier MyClass<int>
mais il ne peut pas voir le modèle MyClass<T>
(seule son interface en foo.h ), il ne peut donc pas le créer.
Si foo.cpp utilise lui-même MyClass<int>
le code correspondant sera généré lors de la compilation. foo.cpp Ainsi, lorsque bar.o est lié à foo.o ils peuvent être branchés et fonctionneront. Nous pouvons utiliser ce fait pour permettre à un ensemble fini d'instanciations de modèles d'être implémentées dans un fichier .cpp en écrivant un seul modèle. Mais il n'y a aucun moyen pour bar.cpp pour utiliser le modèle comme modèle et l'instancier sur les types qu'il souhaite ; il ne peut utiliser que des versions préexistantes de la classe modèle que l'auteur de foo.cpp de l'idée de fournir.
On pourrait penser que lors de la compilation d'un modèle, le compilateur devrait "générer toutes les versions", celles qui ne sont jamais utilisées étant filtrées lors de l'édition des liens. Outre l'énorme surcharge et les difficultés extrêmes auxquelles une telle approche serait confrontée, étant donné que les "modificateurs de type" tels que les pointeurs et les tableaux permettent même aux types intégrés de donner naissance à un nombre infini de types, que se passe-t-il lorsque j'étend mon programme en ajoutant :
- baz.cpp
- déclare et met en œuvre
class BazPrivate
et utilise MyClass<BazPrivate>
Il n'y a aucune chance que cela fonctionne, à moins de
- Il faut recompiler foo.cpp chaque fois que nous changeons tout autre fichier du programme au cas où il ajouterait une nouvelle instanciation de
MyClass<T>
- Exiger que baz.cpp contient (éventuellement par le biais de l'en-tête) le modèle complet de
MyClass<T>
afin que le compilateur puisse générer MyClass<BazPrivate>
lors de la compilation des baz.cpp .
Personne n'aime (1), parce que les systèmes de compilation par analyse de programmes entiers nécessitent un temps d'adaptation important. pour toujours et parce qu'il est impossible de distribuer des bibliothèques compilées sans le code source. Nous avons donc (2) à la place.
27 votes
S'il est vrai que placer toutes les définitions des fonctions de template dans le fichier d'en-tête est probablement la façon la plus pratique de les utiliser, on ne voit toujours pas clairement ce que fait "inline" dans cette citation. Il n'est pas nécessaire d'utiliser des fonctions en ligne pour cela. "Inline" n'a absolument rien à voir avec cela.
15 votes
Le livre est périmé.
16 votes
Un modèle n'est pas comme une fonction qui peut être compilée en code d'octets. Il s'agit simplement d'un modèle permettant de générer une telle fonction. Si vous placez un modèle seul dans un fichier *.cpp, il n'y a rien à compiler. De plus, l'instanciation explicite n'est en fait pas un modèle, mais le point de départ de la création d'une fonction à partir du modèle, qui se retrouve dans le fichier *.obj.
40 votes
Suis-je le seul à penser que le concept de template est paralysé en C++ à cause de cela ?
2 votes
@AnT peut-être ont-ils voulu dire "inline" non pas en tant que mot-clé mais plutôt en tant que "méthodes implémentées à l'endroit de la déclaration, à l'intérieur de la classe".