Pourquoi les modèles ne peuvent-ils être implémentés que dans le fichier d'en-tête?

Citation de Le C++ standard library: un tutoriel et guide :

la seule façon portable d'utiliser les gabarits à l'heure actuelle est de les implémenter dans les fichiers d'en-tête en utilisant des fonctions inline.

pourquoi?

(Clarification: les fichiers d'en-tête ne sont pas la solution portable seulement . Mais ils sont la solution portable la plus commode.)

1428
demandé sur Aaron McDaid 2009-01-30 13:06:50
la source

14 ответов

Il est pas nécessaire de mettre l'application dans le fichier d'en-tête, voir la solution de rechange à la fin de cette réponse.

quoi qu'il en soit, la raison pour laquelle votre code échoue est que, lors de l'instanciation d'un template, le compilateur crée une nouvelle classe avec l'argument template donné. Par exemple:

template<typename T>
struct Foo
{
    T bar;
    void doSomething(T param) {/* do stuff using T */}
};

// somewhere in a .cpp
Foo<int> f; 

en lisant cette ligne, le compilateur va créer une nouvelle classe (appelons-la FooInt ), qui est équivalant à ce qui suit:

struct FooInt
{
    int bar;
    void doSomething(int param) {/* do stuff using int */}
}

par conséquent, le compilateur doit avoir accès à l'implémentation des méthodes, pour les instancier avec l'argument template (dans ce cas int ). Si ces implémentations n'étaient pas dans l'en-tête, elles ne seraient pas accessibles, et donc le compilateur ne pourrait pas instancier le modèle.

une solution courante à cela est d'écrire la déclaration de modèle dans un fichier d'en-tête, puis implémenter la classe dans un fichier d'implémentation (par exemple .tpp), et inclure ce fichier de mise en œuvre à la fin de l'en-tête.

// Foo.h
template <typename T>
struct Foo
{
    void doSomething(T param);
};

#include "Foo.tpp"

// Foo.tpp
template <typename T>
void Foo<T>::doSomething(T param)
{
    //implementation
}

ainsi, l'implémentation est encore séparée de la déclaration, mais est accessible au compilateur.

une autre solution est de garder l'implémentation séparée, et d'instancier explicitement toutes les instances de template dont vous aurez besoin:

// Foo.h

// no implementation
template <typename T> struct Foo { ... };

//----------------------------------------    
// Foo.cpp

// implementation of Foo's methods

// explicit instantiations
template class Foo<int>;
template class Foo<float>;
// You will only be able to use Foo with int or float

Si mon explication n'est pas claire assez, vous pouvez jeter un oeil à la C++ Super-FAQ sur ce sujet .

1245
répondu Luc Touraille 2018-08-07 16:06:55
la source

Beaucoup de bonnes réponses ici, mais je voulais ajouter ceci (pour l'exhaustivité):

si vous, au bas du fichier CPP implementation, faites une instanciation explicite de tous les types avec lesquels le modèle sera utilisé, le linker pourra les trouver comme d'habitude.

Edit: ajout d'un exemple d'instanciation de modèle explicite. Utilisé après que le modèle a été défini, et toutes les fonctions de membre ont été définies.

template class vector<int>;

ceci instanciera (et mettra ainsi à la disposition du linker) la classe et toutes ses fonctions de membre (seulement). Une syntaxe similaire fonctionne pour les fonctions de modèle, donc si vous avez des surcharges d'opérateur non-membre, vous pourriez avoir besoin de faire la même chose pour celles-ci.

l'exemple ci-dessus est assez inutile puisque le vecteur est entièrement défini dans les en-têtes, sauf lorsqu'un fichier include commun (en-tête précompilé?) utilise extern template class vector<int> pour l'empêcher de l'instancier dans tout le autre (1000?) les fichiers qui utilisent le vecteur.

206
répondu MaHuJa 2013-02-14 18:26:27
la source

c'est à cause de l'exigence de compilation séparée et parce que les gabarits sont un polymorphisme de style instanciation.

permet de se rapprocher un peu du béton pour une explication. Dites que j'ai les fichiers suivants:

  • foo.h
    • déclare l'interface de class MyClass<T>
  • foo.rpc
    • définit la mise en œuvre de class MyClass<T>
  • bar.rpc
    • utilise MyClass<int>

compilation séparée signifie que je devrais pouvoir compiler foo.rpc indépendamment de bar.rpc . 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 de faire l'ensemble-l'analyse du programme. C'est seulement le linker qui doit gérer le programme entier à la fois, et le travail du linker est considérablement plus facile.

bar.cpp n'a même pas besoin d'exister quand je compile foo.cpp , mais je devrais quand même pouvoir relier le foo.o j'avais déjà avec la barre .o je viens juste de produire, sans avoir besoin de recompiler foo.rpc . foo.rpc pourrait même être compilé dans une bibliothèque dynamique, distribué ailleurs sans foo.cpp , et lié avec le code ils écrivent des années après que j'ai écrit foo.rpc .

"Instanciation de style polymorphisme", signifie que le modèle MyClass<T> n'est pas vraiment une classe générique qui peut être compilé en code qui peut travailler pour n'importe quelle valeur de T . Cela ajouterait des frais généraux tels que la boxe, le besoin de passer des pointeurs de fonction à allocateurs et les constructeurs, etc. L'intention des modèles C++ est d'éviter d'avoir à écrire presque identique class MyClass_int , class MyClass_float , etc, mais d'être encore en mesure de se retrouver avec du code compilé qui est la plupart du temps comme si nous avait écrit chaque version séparément. Si un modèle est , littéralement un modèle, un modèle de classe est pas d'une classe, c'est une recette pour la création d'une nouvelle classe pour chaque T 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é.

quand foo.cpp est compilé, le compilateur ne peut pas voir la barre .rpc savoir que MyClass<int> est nécessaire. On peut voir le modèle MyClass<T> , mais il ne peut pas émettre de code (c'est un modèle, pas une classe). Et quand bar.rpc est compilé, le compilateur peut voir qu'il a besoin de créer un MyClass<int> , mais il ne peut pas voir le modèle MyClass<T> (seulement son interface dans foo.h ) donc il ne peut pas le créer.

Si foo.cpp lui-même utilise MyClass<int> , puis le code pour qui sera généré lors de la compilation foo.cpp , donc quand barre.o est lié à foo.o ils peuvent être connectés et fonctionneront. Nous pouvons utiliser ce fait pour permettre à un ensemble fini d'instanciations de template d'être implémenté dans un .produire un fichier RPC en rédigeant un modèle unique. Mais il n'y a pas de place pour le bar .cpp pour utiliser le template comme template et l'instancier sur les types qu'il aime; il ne peut utiliser que des versions préexistantes de la classe templated que l'auteur de foo.rpc pensé à fournir.

Vous pourriez penser que lors de la compilation d'un template le compilateur devrait "générer toutes les versions", celles qui ne sont jamais utilisées étant filtrées pendant le lien. Mis à part les énormes frais généraux et les difficultés extrêmes qu'une telle approche rencontrerait parce que les caractéristiques de "modificateur de type" comme les pointeurs et les tableaux permettent même juste les types intégrés pour donner lieu à un nombre infini de types, ce qui se passe quand j'étend maintenant mon programme en ajoutant:

  • baz.rpc
    • déclare et met en œuvre class BazPrivate , et utilise MyClass<BazPrivate>
  • il n'y a aucun moyen que cela puisse fonctionner à moins que nous ne soyons

    1. doivent recompiler foo.cpp chaque fois que nous changeons tout autre fichier dans le programme , au cas où il a ajouté une nouvelle instanciation de MyClass<T>
    2. Exiger baz.cpp contient (éventuellement via header includes) le modèle complet de MyClass<T> , de sorte que le compilateur peut générer MyClass<BazPrivate> lors de la compilation de baz.rpc .

    personne n'aime (1), parce qu'il faut forever pour compiler , et parce qu'il rend impossible de distribuer des bibliothèques compilées sans le code source. Nous avons donc (2) à la place.

179
répondu Ben 2013-05-11 15:26:56
la source

Modèles doivent être instancié par le compilateur avant de le compiler en code objet. Cette instanciation ne peut être réalisée que si les arguments de template sont connus. Imaginez maintenant un scénario où une fonction de modèle est déclarée dans a.h , définie dans a.cpp et utilisée dans b.cpp . Lorsque a.cpp est compilé, il n'est pas forcément connu que la prochaine compilation b.cpp nécessitera une instance du modèle, laissez seule instance spécifique serait-ce. Pour plus d'en-tête et de fichiers source, la situation peut rapidement devenir plus compliquée.

on peut avancer que les compilateurs peuvent être rendus plus intelligents pour" regarder vers l'avenir " pour toutes les utilisations du modèle, mais je suis sûr qu'il ne serait pas difficile de créer des scénarios récursifs ou autrement compliqués. AFAIK, les compilateurs ne font pas de telles têtes. Comme Anton l'a souligné, certains compilateurs soutiennent les déclarations d'exportation explicites des instanciations de template, mais tous les compilateurs ne le supportent pas (encore?).

66
répondu David Hanak 2016-08-04 10:41:11
la source

en fait, les versions de la norme C++ avant C++11 défini le mot-clé" exporter", que serait rendre possible de simplement déclarer des gabarits dans un fichier d'en-tête et de les mettre en œuvre ailleurs.

malheureusement, aucun des compilateurs populaires n'a implémenté ce mot clé. Le seul que je connaisse est la fronde écrite par Edison Design Group, qui est utilisée par le compilateur Comeau C++. Tous les autres ont insisté pour que vous écriviez des modèles dans l'en-tête fichiers, nécessitant la définition du code pour une instanciation correcte (comme d'autres l'ont déjà souligné).

en conséquence, le Comité de la norme ISO C++ a décidé de supprimer la caractéristique export des modèles commençant par C++11.

49
répondu DevSolar 2018-02-11 00:21:58
la source

bien que standard c++ n'ait pas cette exigence, certains compilateurs exigent que tous les modèles de fonction et de classe soient disponibles dans chaque unité de traduction qu'ils sont utilisés. En effet, pour ces compilateurs, les corps des fonctions de template doivent être disponibles dans un fichier d'en-tête. Répéter: cela signifie que ces compilateurs ne permettront pas qu'ils soient définis dans des fichiers non-en-tête tels que .fichiers cpp

il y a un exportation mot-clé qui est censé atténuer ce problème, mais il est loin d'être portable.

32
répondu Anton Gogolev 2013-09-19 21:16:55
la source
Les gabarits

doivent être utilisés dans les en-têtes car le compilateur doit instancier différentes versions du code, en fonction des paramètres donnés/déduits pour les paramètres du gabarit. Rappelez-vous qu'un modèle ne représente pas le code directement, mais un modèle pour plusieurs versions de ce code. Lorsque vous compilez une fonction non-template dans un fichier .cpp , vous compilez une fonction/classe concrète. Ce n'est pas le cas pour les gabarits, qui peuvent être instanciés avec différents types, à savoir, du code de béton doit être émis lors du remplacement des paramètres de gabarit par des types de béton.

il y avait une caractéristique avec le mot-clé export qui était destiné à être utilisé pour la compilation séparée. La fonctionnalité export est dépréciée dans C++11 et, AFAIK, un seul compilateur l'a implémentée. Vous ne devriez pas utiliser export . Une compilation séparée n'est pas possible dans C++ ou C++11 mais peut-être dans C++17 , si les concepts le font, nous pourrait avoir une certaine manière de compilation séparée.

pour qu'une compilation séparée soit réalisée, il doit être possible de procéder à une vérification distincte du corps du modèle. Il semble qu'une solution soit possible avec des concepts. Jetez un oeil à ce papier récemment présenté à la réunion du Comité des normes. Je pense que ce n'est pas la seule exigence, puisque vous avez encore besoin d'instanciate code pour le code de modèle dans le code d'utilisateur.

la compilation séparée problème pour les modèles je suppose que c'est aussi un problème découlant de la migration de modules, qui est actuellement en cours d'élaboration.

26
répondu Germán Diago 2013-05-12 20:48:42
la source

cela signifie que la façon la plus portable de définir les implémentations de méthode des classes de gabarits est de les définir à l'intérieur de la définition de classe de gabarits.

template < typename ... >
class MyClass
{

    int myMethod()
    {
       // Not just declaration. Add method implementation here
    }
};
13
répondu Benoît 2009-02-11 00:44:15
la source

bien qu'il y ait beaucoup de bonnes explications ci-dessus, je manque un moyen pratique pour séparer les gabarits dans l'en-tête et le corps.

Mon principal souci est d'éviter la recompilation de tous les utilisateurs de template, lorsque je modifie sa définition.

Avoir toutes les instanciations de template dans le corps du template n'est pas une solution viable pour moi, puisque l'auteur du template peut ne pas tout savoir si son usage et l'utilisateur du template peuvent ne pas avoir le droit de le modifier.

J'ai adopté l'approche suivante, qui fonctionne également pour les compilateurs plus anciens (gcc 4.3.4, aCC a. 03.13).

pour chaque utilisation de template, il y a un fichier typedef dans son propre fichier d'en-tête (généré à partir du modèle UML). Son corps contient l'instanciation (qui finit dans une bibliothèque reliée à la fin).

Chaque utilisateur du modèle inclut ce fichier d'en-tête et utilise le typedef.

un exemple schématique:

MyTemplate.h:

#ifndef MyTemplate_h
#define MyTemplate_h 1

template <class T>
class MyTemplate
{
public:
  MyTemplate(const T& rt);
  void dump();
  T t;
};

#endif

MyTemplate.cpp:

#include "MyTemplate.h"
#include <iostream>

template <class T>
MyTemplate<T>::MyTemplate(const T& rt)
: t(rt)
{
}

template <class T>
void MyTemplate<T>::dump()
{
  cerr << t << endl;
}

MyInstantiatedTemplate.h:

#ifndef MyInstantiatedTemplate_h
#define MyInstantiatedTemplate_h 1
#include "MyTemplate.h"

typedef MyTemplate< int > MyInstantiatedTemplate;

#endif

MyInstantiatedTemplate.cpp:

#include "MyTemplate.cpp"

template class MyTemplate< int >;

principal.cpp:

#include "MyInstantiatedTemplate.h"

int main()
{
  MyInstantiatedTemplate m(100);
  m.dump();
  return 0;
}

de cette façon, seules les instanciations de template devront être recompilées, pas tous les utilisateurs de template (et les dépendances).

9
répondu lafrecciablu 2016-05-12 17:08:56
la source

C'est exactement correct parce que le compilateur doit savoir quel type c'est pour l'allocation. Donc, le modèle des classes, des fonctions, des énumérations,etc.. doit être implémentée aussi bien dans le fichier d'en-tête si elle doit être rendue publique ou faire partie d'une bibliothèque (statique ou dynamique) parce que les fichiers d'en-tête ne sont pas compilés contrairement aux fichiers c/cpp qui le sont. Si le compilateur ne connaît pas le type ne peut pas compiler. Dans .Net il peut Car tous les objets dérivent de la classe Object. Ce n'est pas le cas .Net.

6
répondu Robert 2011-09-17 16:27:39
la source

si la préoccupation est le temps de compilation supplémentaire et la taille binaire bloat produit par la compilation de la .h dans le cadre de tous les .modules cpp en l'utilisant, dans de nombreux cas, ce que vous pouvez faire est de faire descendre la classe template d'une classe de base non-templatisée pour les parties non dépendantes du type de l'interface, et cette classe de base peut avoir son implémentation dans le .fichier cpp.

6
répondu Eric Shaw 2016-07-27 08:01:10
la source

une façon d'avoir une mise en œuvre séparée est la suivante.

//inner_foo.h

template <typename T>
struct Foo
{
    void doSomething(T param);
};


//foo.tpp
#include "inner_foo.h"
template <typename T>
void Foo<T>::doSomething(T param)
{
    //implementation
}


//foo.h
#include <foo.tpp>

//main.cpp
#include <foo.h>

inner_foo contient les déclarations forward. foo.tpp a la mise en œuvre et comprend inner_foo.h; et foo.h aura juste une ligne, pour inclure foo.PPT.

Sur le moment de la compilation, du contenu de toto.H sont copiés sur foo.tpp et ensuite tout le fichier est copié sur foo.h après quoi il compile. De cette façon, il n'y a pas de limites, et l'appellation est conforme, en échange d'un fichier supplémentaire.

je fais cela parce que les analyseurs statiques pour le code break ne voient pas les déclarations vers l'avant de la classe in *.PPT. Ceci est gênant lors de l'écriture de code dans n'importe quel IDE ou en utilisant YouCompleteMe ou d'autres.

2
répondu Pranay 2017-05-13 04:42:11
la source

le compilateur générera du code pour chaque instanciation de template lorsque vous utilisez un template pendant l'étape de compilation. Dans le processus de compilation et de mise en relation .les fichiers cpp sont convertis en pur code objet ou machine qui contient des références ou des symboles non définis parce que le .h fichiers qui sont inclus dans votre main.rpc ont pas de mise en œuvre ENCORE. Ceux-ci sont prêts à être liés avec un autre fichier objet qui définit une implémentation pour votre modèle et vous avez donc un un.hors de l'exécutable. Cependant, puisque les modèles doivent être traités dans l'étape de compilation afin de générer du code pour chaque instanciation de modèle que vous faites dans votre programme principal, le lien n'aidera pas parce que compiler le principal.rpc en main.O et puis compiler votre modèle .rpc dans le modèle.o et puis de la liaison de ne pas atteindre les modèles de but parce que je suis reliant les différents instanciation d'un modèle pour le même modèle de mise en œuvre de! Et les modèles sont censés faire le contraire, j'.e avoir Une implémentation mais permet de nombreuses instanciations disponibles via l'utilisation d'une classe.

veut dire typename T remplacé pendant l'étape de compilation et non pas l'étape de lien, donc si j'essaie de compiler un modèle sans que T soit remplacé comme un type de valeur concret, alors ça ne marchera pas parce que c'est la définition des modèles, c'est un processus de compilation, et la méta-programmation de btw consiste à utiliser cette définition.

1
répondu Moshe Rabaev 2017-03-16 05:16:18
la source

juste pour ajouter quelque chose de remarquable ici. On peut définir les méthodes d'une classe templated juste très bien dans le fichier d'implémentation quand elles ne sont pas des modèles de fonction.


myQueue.hpp:

template <class T> 
class QueueA {
    int size;
    ...
public:
    template <class T> T dequeue() {
       // implementation here
    }

    bool isEmpty();

    ...
}    

myQueue.cpp:

// implementation of regular methods goes like this:
template <class T> bool QueueA<T>::isEmpty() {
    return this->size == 0;
}


main()
{
    QueueA<char> Q;

    ...
}
1
répondu Nik-Lz 2018-07-19 03:49:05
la source

Autres questions sur c++ templates c++-faq