1705 votes

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

Citation de La norme C++ de la bibliothèque: un tutoriel et guide:

Le seul portable manière d'utiliser les modèles du moment, c'est pour les mettre en œuvre dans les fichiers d'en-tête en utilisant les fonctions inline.

Pourquoi est-ce?

1484voto

Luc Touraille Points 29252

Parce que lors de l'instanciation d'un modèle, le compilateur crée une nouvelle classe avec le modèle donné en argument. 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 - FooInt), qui est équivalente à la suivante:

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

Par conséquent, le compilateur a besoin d'avoir accès à la mise en œuvre des méthodes, de les instancier avec l'argument de modèle (dans ce cas - int). Si ces projets n'étaient pas dans l'en-tête, qu'ils ne serait pas accessible, et, par conséquent, le compilateur ne serait pas en mesure d'instancier le modèle.

Une solution commune à ce qui est d'écrire le modèle de la déclaration dans un fichier d'en-tête, puis de mettre en œuvre la classe dans un fichier d'implémentation (par exemple .tpp), et d'inclure ce fichier de mise en oeuvre à 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, la mise en œuvre est encore séparé de la déclaration, mais n'est accessible pour le compilateur.

Une autre solution est de garder la mise en œuvre séparés, et d'instancier explicitement toutes les instances de modèle, vous aurez besoin de:

// 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 clair, vous pouvez jeter un oeil sur le C++ FaqLite sur ce sujet.

244voto

MaHuJa Points 890

Beaucoup de bonnes réponses ici, mais je voulais ajouter ceci (pour être complet):

Si vous, en bas de la mise en œuvre de fichier cpp, ne instanciation explicite de tous les types, le modèle sera utilisé, l'éditeur de liens serez en mesure de trouver, comme d'habitude.

Edit: Ajout de l'exemple explicite de l'instanciation d'un modèle. Utilisé une fois que le modèle a été défini et toutes les fonctions de membre a été défini.

template class vector<int>;

Cela permettra d'instancier (et ainsi mettre à la disposition de l'éditeur de liens) de la classe et l'ensemble de ses fonctions de membre (uniquement). La même syntaxe que des œuvres pour le modèle de fonctions, donc si vous avez des non-membre de la surcharge de l'opérateur, vous pouvez faire la même chose pour ceux qui.

L'exemple ci-dessus est assez inutile, puisque le vecteur est défini plus en détail dans les en-têtes, sauf lorsqu'une commune d'inclure le fichier (en-tête précompilé?) utilise extern template class vector<int> afin de le tenir à partir de l'instanciation dans tous les autres (1000?) les fichiers qui utilisent le vecteur.

239voto

Ben Points 22160

C'est à cause de l'exigence de compilation séparée et parce que les modèles sont instanciation de style polymorphisme.

Permet d'obtenir un peu plus de béton pour une explication. Dire que j'ai les fichiers suivants:

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

Compilation séparée signifie que je devrais être en mesure de compiler foo.cpp indépendamment de bar.cpp. Le compilateur fait tout le travail dur de l'analyse, l'optimisation et la génération de code sur chaque unité de compilation de manière totalement indépendante; nous n'avons pas besoin de le faire ensemble-l'analyse du programme. Ce n'est que le linker qui doit gérer l'ensemble du programme à la fois, et l'éditeur de liens de travail est beaucoup plus simple.

bar.cpp n'a même pas besoin d'exister quand je compile foo.cppmais je dois encore être en mesure de relier les foo.o j'ai déjà eu avec la barre.o je viens juste de produit, sans avoir besoin de recompiler foo.cpp. foo.cpp pourrait même être compilé dans une bibliothèque dynamique, distribué ailleurs sans foo.cpp, et en lien avec le code qu'ils écrivent des années après que j'ai écrit foo.cpp.

"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. Qui permettrait d'ajouter les frais généraux tels que la boxe, besoin de passer des pointeurs de fonction à allocateurs et les constructeurs, etc. L'intention de modèles C++ est pour éviter d'avoir à écrire à peu près identique class MyClass_int, class MyClass_float,, etc, mais encore être en mesure de se retrouver avec le code compilé qui est la plupart du temps comme si on avait écrit chaque version séparément. Si un modèle est littéralement un modèle, un modèle de classe n'est pas une classe, c'est une recette pour la création d'une nouvelle classe pour chaque T nous rencontrer. Un modèle ne peut pas être compilé en code, seul le résultat de l'instanciation du modèle peut être compilé.

Alors, quand foo.cpp est compilé, le compilateur ne peut pas voir bar.cpp de savoir qu' 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.cpp 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 qui sera généré lors de la compilation foo.cpp, de sorte que lorsque la barre.o est lié à toto.o ils peuvent être accrochées et de travail. Nous pouvons utiliser ce fait pour permettre à un ensemble fini de modèle instanciations à être mis en œuvre dans un .fichier cpp par l'écriture d'un modèle unique. Mais il n'y a aucun moyen pour bar.cpp pour utiliser le modèle comme un modèle et d'instancier sur tout les types qu'il aime; il ne peut utiliser les pré-versions existantes de la basé sur un modèle de classe que l'auteur de foo.cpp pensé à fournir.

Vous pourriez penser que lors de la compilation d'un modèle le compilateur doit "générer toutes les versions", avec ceux qui ne sont jamais utilisés étant filtrés lors de la liaison. En dehors de la surcharge énorme et l'extrême difficulté d'une telle approche permettrait de faire face en raison de "modificateur de type" fonctionnalités comme les pointeurs et les tableaux permettent même juste les types intégrés pour donner naissance à un nombre infini de types, ce qui se passe quand j'ai aujourd'hui d'étendre mon programme en ajoutant:

  • baz.cpp
    • déclare et implémente class BazPrivate, et les utilisations MyClass<BazPrivate>

Il n'est pas possible que cela pourrait fonctionner, à moins que l'on soit

  1. A recompiler foo.cpp chaque fois que nous changeons tout autre fichier dans le programme, en cas d'ajout d'un nouveau roman de l'instanciation de l' MyClass<T>
  2. Exiger que baz.cpp contient (éventuellement via l'en-tête comprend) le modèle complet de l' MyClass<T>, de sorte que le compilateur peut générer MyClass<BazPrivate> lors de la compilation de baz.cpp.

Personne n'aime (1), parce que l'ensemble du programme d'analyse des systèmes de compilation prenez jamais à la compilation , et parce qu'il est impossible de distribuer des bibliothèques compilées sans le code source. Nous avons donc (2) à la place.

78voto

David Hanak Points 5960

Les modèles doivent être instancié par le compilateur avant de le compiler en code objet. Cette instanciation ne peut être atteint que si les arguments de modèle sont connus. Maintenant, imaginez un scénario où un avec fonction est déclarée en a.h, défini en a.cpp et utilisés en b.cpp. Lors de l' a.cpp est compilé, il n'est pas impérativement connu que la prochaine compilation b.cpp nécessiteront une instance du modèle, de la laisser seule instance spécifique serait-ce. Pour plus d'en-tête et les fichiers source, la situation peut rapidement devenir plus compliqués.

On peut dire que compiers peut être rendu plus intelligent de "look ahead" pour toutes les utilisations du modèle, mais je suis sûr qu'il ne serait pas difficile de créer récursive ou autrement compliqué scénarios. Autant que je sache, en cas de déviation de ne pas faire ce genre de look recherches en avant. Anton souligné, certains compilateurs soutien explicite à l'exportation decarations de avec les instanciations, mais pas tous les compilateurs de soutien (encore?).

59voto

DevSolar Points 18897

En fait, le standard C++ définit le "exporter" mot-clé qui permettrait de le rendre possible de simplement déclarer les modèles dans un fichier d'en-tête et de mettre en œuvre ailleurs.

Malheureusement, aucun de l'populaires compilateurs met en œuvre ce mot clé. Le seul que je connaisse, c'est le frontend écrit par Edison Groupe de Conception, qui est utilisé par le Comeau compilateur C++. Tous les autres sont coincés avoir à écrire de modèles dans les fichiers d'en-tête, parce que le compilateur a besoin de la définition du code de bonne instanciation (comme d'autres ont souligné déjà).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X