code - template method c++ cpp




Pourquoi les modèles peuvent-ils être implémentés uniquement dans le fichier d'en-tête? (9)

Citation de la bibliothèque standard C ++: un tutoriel et un manuel :

La seule façon portable d'utiliser des modèles pour le moment est de les implémenter dans des fichiers d'en-tête en utilisant des fonctions en ligne.

Pourquoi est-ce?

(Clarification: les fichiers d'en-tête ne sont pas la seule solution portable, mais ils sont la solution portable la plus pratique.)


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

Si vous, en bas du fichier cpp d'implémentation, instanciez explicitement tous les types avec lesquels le template sera utilisé, l'éditeur de liens sera capable de les trouver comme d'habitude.

Edit: Ajout d'un exemple d'instanciation de modèle explicite. Utilisé après la définition du modèle et toutes les fonctions membres ont été définies.

template class vector<int>;

Cela va instancier (et donc mettre à la disposition de l'éditeur de liens) la classe et toutes ses fonctions membres (seulement). Une syntaxe similaire fonctionne pour les fonctions de modèle, donc si vous avez des surcharges d'opérateurs non membres, vous devrez peut-être 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 d'inclusion commun (en-tête précompilé?) Utilise le extern template class vector<int> pour l'empêcher de l'instancier dans tous les autres fichiers qui utilisent le vecteur.


Bien que le standard C ++ n'ait pas cette exigence, certains compilateurs exigent que tous les modèles de fonctions et de classes doivent être disponibles dans chaque unité de traduction utilisée. En effet, pour ces compilateurs, les corps des fonctions du modèle doivent être rendus disponibles dans un fichier d'en-tête. Pour répéter: cela signifie que ces compilateurs ne permettront pas de les définir dans des fichiers non-en-tête tels que les fichiers .cpp

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


C'est exactement correct car le compilateur doit savoir de quel type il s'agit pour l'allocation. Donc, les classes, fonctions, énumérations, etc .. doivent également être implémentées dans le fichier header si elles doivent être rendues publiques ou faire partie d'une bibliothèque (statique ou dynamique) car les fichiers d'en-tête ne sont PAS compilés contrairement aux fichiers c / cpp sont. Si le compilateur ne connaît pas le type, il ne peut pas le compiler. En .Net, cela est possible parce que tous les objets dérivent de la classe Object. Ce n'est pas .Net.


Cela signifie que la façon la plus portable de définir des implémentations de méthodes de classes de modèles consiste à les définir dans la définition de classe de modèle.

template < typename ... >
class MyClass
{

    int myMethod()
    {
       // Not just declaration. Add method implementation here
    }
};

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

Quoi qu'il en soit, la raison de l'échec de votre code est que, lors de l'instanciation d'un modèle, 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; 

Lors de la lecture de cette ligne, le compilateur va créer une nouvelle classe (appelons-la FooInt ), ce qui équivaut à 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 par conséquent le compilateur ne serait pas capable d'instancier le modèle.

Une solution courante consiste à é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 d'implémentation à 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
}

De cette façon, l'implémentation est toujours séparée de la déclaration, mais est accessible au compilateur.

Une autre solution consiste à séparer l'implémentation et à instancier explicitement toutes les instances de modèle dont vous avez 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 assez claire, vous pouvez jeter un oeil à la super-FAQ C ++ sur ce sujet .


Le compilateur générera du code pour chaque instanciation de modèle lorsque vous utilisez un modèle pendant l'étape de compilation. Dans le processus de compilation et de liaison, les fichiers .cpp sont convertis en un objet pur ou un code machine qui contient des références ou des symboles indéfinis car les fichiers .h inclus dans votre fichier main.cpp n'ont pas d'implémentation YET. Ceux-ci sont prêts à être liés avec un autre fichier objet qui définit une implémentation pour votre template et donc vous avez un exécutable a.out complet. Cependant, puisque les templates doivent être traités dans l'étape de compilation afin de générer du code pour chaque instanciation de template que vous faites dans votre programme principal, link ne vous aidera pas car compiler le main.cpp dans main.o et ensuite compiler votre template .cpp dans template.o et puis la liaison n'atteindra pas le but des templates parce que je lie une instanciation de template différente à la même implémentation de template! Et les templates sont supposés faire l'inverse c'est-à-dire avoir une seule implémentation mais autoriser beaucoup d'instanciations disponibles via l'utilisation d'une classe.

Signification typename T get est remplacé pendant l'étape de compilation pas l'étape de liaison donc si j'essaye de compiler un template sans T étant remplacé comme un type de valeur concret cela ne fonctionnera pas parce que c'est la définition des templates c'est un processus de compilation, et btw La méta-programmation consiste à utiliser cette définition.


Les modèles 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 modèle. 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-modèle dans un fichier .cpp , vous compilez une fonction / classe concrète. Ce n'est pas le cas pour les modèles, qui peuvent être instanciés avec différents types, à savoir, le code concret doit être émis lors du remplacement des paramètres de modèle par des types concrets.

Il y avait une fonctionnalité avec le mot clé d' export qui devait être utilisé pour une compilation séparée. La fonctionnalité d' export est déconseillée dans C++11 et, AFAIK, un seul compilateur l'a implémenté. Vous ne devriez pas utiliser l' export . La compilation séparée n'est pas possible en C++ ou en C++11 mais peut-être en C++17 , si les concepts l'intègrent, nous pourrions avoir un moyen de compilation séparée.

Pour qu'une compilation séparée soit réalisée, une vérification séparée du corps du gabarit doit être possible. Il semble qu'une solution soit possible avec des concepts. Jetez un oeil à ce paper récemment présenté à la réunion du comité des normes. Je pense que ce n'est pas la seule exigence, car vous devez toujours instancier le code du code du modèle dans le code utilisateur.

Le problème de compilation séparé pour les templates Je suppose que c'est aussi un problème qui se pose avec la migration vers les modules, qui est en cours de traitement.


Même s'il y a beaucoup de bonnes explications ci-dessus, il me manque un moyen pratique de séparer les modèles en en-tête et en corps.
Ma principale préoccupation est d'éviter la recompilation de tous les utilisateurs de modèles, quand je change sa définition.
Avoir toutes les instanciations de gabarit dans le corps du gabarit n'est pas une solution viable pour moi, puisque l'auteur du gabarit peut ne pas tout savoir si son utilisation et l'utilisateur du gabarit n'ont pas le droit de le modifier.
J'ai pris l'approche suivante, qui fonctionne aussi pour les compilateurs plus anciens (gcc 4.3.4, aCC A.03.13).

Pour chaque utilisation de template, il y a un typedef dans son propre fichier d'en-tête (généré à partir du modèle UML). Son corps contient l'instanciation (qui se termine dans une bibliothèque qui est lié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 >;

main.cpp:

#include "MyInstantiatedTemplate.h"

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

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


Un moyen d'avoir une implémentation séparée est le suivant.

//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 a les déclarations en avant. foo.tpp a l'implémentation et inclut inner_foo.h; et foo.h aura juste une ligne, pour inclure foo.tpp.

Au moment de la compilation, le contenu de foo.h est copié dans foo.tpp, puis le fichier entier est copié dans foo.h, après quoi il est compilé. De cette façon, il n'y a aucune limitation, et la dénomination est cohérente, en échange d'un fichier supplémentaire.

Je fais ceci parce que les analyseurs statiques pour le code cassent quand il ne voit pas les déclarations avant de la classe dans * .tpp. Ceci est gênant lors de l'écriture de code dans n'importe quel IDE ou en utilisant YouCompleteMe ou d'autres.







c++-faq