J'ai un code de modèle que je préférerais avoir stocké dans un fichier CPP au lieu d'être intégré dans l'en-tête. Je sais que cela peut être fait tant que vous savez quels types de modèles seront utilisés. Par exemple:
fichier .h
class foo
{
public:
template <typename T>
void do(const T& t);
};
Fichier .cpp
template <typename T>
void foo::do(const T& t)
{
// Do something with t
}
template void foo::do<int>(const int&);
template void foo::do<std::string>(const std::string&);
Notez les deux dernières lignes - la fonction de modèle foo :: do n'est utilisée qu'avec les chaînes ints et std ::, donc ces définitions signifient que l'application sera liée.
Ma question est - est-ce un hack méchant ou cela fonctionnera-t-il avec d'autres compilateurs / linkers? J'utilise uniquement ce code avec VS2008 pour le moment, mais je souhaiterai porter sur d'autres environnements.
do
comme identifiant: ptemplate class foo<int>;template class foo<std::string>;
à la fin du fichier .cpp?Réponses:
Le problème que vous décrivez peut être résolu en définissant le modèle dans l'en-tête ou via l'approche que vous décrivez ci-dessus.
Je recommande de lire les points suivants de la FAQ C ++ Lite :
Ils entrent dans beaucoup de détails sur ces problèmes de modèle (et d'autres).
la source
Pour d'autres sur cette page qui se demandent quelle est la syntaxe correcte (comme je l'ai fait) pour la spécialisation explicite de modèles (ou au moins dans VS2008), c'est la suivante ...
Dans votre fichier .h ...
Et dans votre fichier .cpp
la source
Ce code est bien formé. Il suffit de faire attention à ce que la définition du modèle soit visible au moment de l'instanciation. Pour citer la norme, § 14.7.2.4:
la source
a.cpp
(définissant la fonctiona() {}
) etb.cpp
(définissant la fonctionb() { a() }
), alors cela sera correctement lié. Si j'ai raison, la citation ci-dessus semble ne pas s'appliquer au cas typique ... je me trompe quelque part?inline
fonctionsinline
. La raison étant que sans un ABI C ++ standardisé, il est difficile / impossible de définir l'effet que cela aurait autrement.Cela devrait fonctionner correctement partout où les modèles sont pris en charge. L'instanciation de modèle explicite fait partie de la norme C ++.
la source
Votre exemple est correct mais pas très portable. Il existe également une syntaxe légèrement plus propre qui peut être utilisée (comme indiqué par @ namespace-sid).
Supposons que la classe basée sur des modèles fasse partie d'une bibliothèque à partager. Faut-il compiler d'autres versions de la classe basée sur des modèles? Le responsable de la bibliothèque est-il censé anticiper toutes les utilisations possibles du modèle de la classe?
Une autre approche est une légère variation de ce que vous avez: ajoutez un troisième fichier qui est le fichier d'implémentation / d'instanciation du modèle.
fichier foo.h
fichier foo.cpp
Fichier foo-impl.cpp
La seule mise en garde est que vous devez dire au compilateur de compiler
foo-impl.cpp
au lieu defoo.cpp
compiler ce dernier ne fait rien.Bien sûr, vous pouvez avoir plusieurs implémentations dans le troisième fichier ou avoir plusieurs fichiers d'implémentation pour chaque type que vous souhaitez utiliser.
Cela permet beaucoup plus de flexibilité lors du partage de la classe basée sur des modèles pour d'autres utilisations.
Cette configuration réduit également les temps de compilation pour les classes réutilisées car vous ne recompilez pas le même fichier d'en-tête dans chaque unité de traduction.
la source
foo.cpp
) à partir desquels les versions sont réellement compilées (enfoo-impl.cpp
) et déclarations (enfoo.h
). Je n'aime pas que la plupart des modèles C ++ soient entièrement définis dans des fichiers d'en-tête. Cela est contraire à la norme C / C ++ de paires dec[pp]/h
pour chaque classe / espace de noms / quel que soit le groupe que vous utilisez. Les gens semblent toujours utiliser des fichiers d'en-tête monolithiques simplement parce que cette alternative n'est pas largement utilisée ou connue.h/cpp
paire, même si je devais entourer la liste d'origine des instanciations dans un garde d'inclusion, mais je pouvais toujours compiler lefoo.cpp
comme d'habitude. Cependant, je suis encore assez nouveau en C ++ et je serais intéressé de savoir si cette utilisation mixte a une mise en garde supplémentaire.foo.cpp
etfoo-impl.cpp
. Ne pas#include "foo.cpp"
dans lefoo-impl.cpp
fichier; au lieu de cela, ajoutez la déclarationextern template class foo<int>;
àfoo.cpp
pour empêcher le compilateur d'instancier le modèle lors de la compilationfoo.cpp
. Assurez-vous que le système de génération crée les deux.cpp
fichiers et transmet les deux fichiers objet à l'éditeur de liens. Cela présente de multiples avantages: a) il est clairfoo.cpp
qu'il n'y a pas d'instanciation; b) les modifications de foo.cpp ne nécessitent pas de recompilation de foo-impl.cpp.foo.cpp
enfoo_impl.h
etfoo-impl.cpp
en justefoo.cpp
. J'ajouterais également des typedefs pour les instanciations defoo.cpp
àfoo.h
, de mêmeusing foo_int = foo<int>;
. L'astuce consiste à fournir aux utilisateurs deux interfaces d'en-tête pour un choix. Lorsque l'utilisateur a besoin d'une instanciation prédéfinie, il inclutfoo.h
, lorsque l'utilisateur a besoin de quelque chose de désordonné, il inclutfoo_impl.h
.Ce n'est certainement pas un hack désagréable, mais sachez que vous devrez le faire (la spécialisation de modèle explicite) pour chaque classe / type que vous souhaitez utiliser avec le modèle donné. Dans le cas de nombreux types de demande d'instanciation de modèle, il peut y avoir BEAUCOUP de lignes dans votre fichier .cpp. Pour résoudre ce problème, vous pouvez avoir un TemplateClassInst.cpp dans chaque projet que vous utilisez afin d'avoir un meilleur contrôle sur les types qui seront instanciés. Évidemment, cette solution ne sera pas parfaite (alias la balle d'argent) car vous pourriez finir par casser l'ODR :).
la source
Il existe, dans la dernière norme, un mot-clé (
export
) qui aiderait à résoudre ce problème, mais il n'est implémenté dans aucun compilateur que je connaisse, à part Comeau.Voir la FAQ à ce sujet.
la source
Il s'agit d'un moyen standard de définir des fonctions de modèle. Je pense qu'il y a trois méthodes que je lis pour définir des modèles. Ou probablement 4. Chacun avec des avantages et des inconvénients.
Définissez dans la définition de classe. Je n'aime pas ça du tout parce que je pense que les définitions de classe sont strictement pour référence et devraient être faciles à lire. Cependant, il est beaucoup moins compliqué de définir des modèles en classe qu'à l'extérieur. Et toutes les déclarations de modèle ne sont pas au même niveau de complexité. Cette méthode fait également du modèle un véritable modèle.
Définissez le modèle dans le même en-tête, mais en dehors de la classe. C'est ma façon préférée la plupart du temps. Il garde votre définition de classe bien rangée, le modèle reste un vrai modèle. Cependant, cela nécessite une dénomination complète du modèle, ce qui peut être délicat. De plus, votre code est accessible à tous. Mais si vous avez besoin que votre code soit en ligne, c'est la seule façon. Vous pouvez également accomplir cela en créant un fichier .INL à la fin de vos définitions de classe.
Incluez le header.h et la mise en œuvre.CPP dans votre main.CPP. Je pense que c'est comme ça que c'est fait. Vous n'aurez pas à préparer de pré-instanciations, il se comportera comme un vrai modèle. Le problème que j'ai avec ça, c'est que ce n'est pas naturel. Normalement, nous n'incluons pas et nous prévoyons d'inclure des fichiers source. Je suppose que puisque vous avez inclus le fichier source, les fonctions du modèle peuvent être intégrées.
Cette dernière méthode, qui était la méthode publiée, définit les modèles dans un fichier source, tout comme le numéro 3; mais au lieu d'inclure le fichier source, nous pré-instancions les modèles à ceux dont nous aurons besoin. Je n'ai aucun problème avec cette méthode et elle est parfois utile. Nous avons un gros code, il ne peut pas bénéficier d'être intégré, alors mettez-le simplement dans un fichier CPP. Et si nous connaissons des instanciations communes et que nous pouvons les prédéfinir. Cela nous évite d'écrire essentiellement la même chose 5, 10 fois. Cette méthode a l'avantage de garder notre code propriétaire. Mais je ne recommande pas de mettre de minuscules fonctions régulièrement utilisées dans les fichiers CPP. Cela réduira les performances de votre bibliothèque.
Remarque, je ne suis pas au courant des conséquences d'un fichier obj gonflé.
la source
Oui, c'est la manière standard de faire une instanciation explicite de
spécialisation. Comme vous l'avez indiqué, vous ne pouvez pas instancier ce modèle avec d'autres types.Edit: corrigé en fonction du commentaire.
la source
Prenons un exemple, disons pour une raison quelconque que vous voulez avoir une classe de modèle:
Si vous compilez ce code avec Visual Studio - cela fonctionne immédiatement. gcc produira une erreur de l'éditeur de liens (si le même fichier d'en-tête est utilisé à partir de plusieurs fichiers .cpp):
Il est possible de déplacer l'implémentation vers un fichier .cpp, mais vous devez ensuite déclarer une classe comme celle-ci -
Et puis .cpp ressemblera à ceci:
Sans deux dernières lignes dans le fichier d'en-tête - gcc fonctionnera bien, mais Visual studio produira une erreur:
la syntaxe de classe de modèle est facultative dans le cas si vous souhaitez exposer la fonction via l'exportation .dll, mais cela ne s'applique qu'à la plate-forme Windows - donc test_template.h pourrait ressembler à ceci:
avec le fichier .cpp de l'exemple précédent.
Cependant, cela donne plus de maux de tête au lieur, il est donc recommandé d'utiliser l'exemple précédent si vous n'exportez pas la fonction .dll.
la source
Il est temps pour une mise à jour! Créez un fichier en ligne (.inl, ou probablement tout autre) et copiez simplement toutes vos définitions dedans. Assurez-vous d'ajouter le modèle au-dessus de chaque fonction (
template <typename T, ...>
). Maintenant, au lieu d'inclure le fichier d'en-tête dans le fichier en ligne, vous faites le contraire. Incluez le fichier en ligne après la déclaration de votre classe (#include "file.inl"
).Je ne sais pas vraiment pourquoi personne n'a mentionné cela. Je ne vois aucun inconvénient immédiat.
la source
#include "file.inl"
, le préprocesseur va coller le contenu defile.inl
directement dans l'en-tête. Quelle que soit la raison pour laquelle vous vouliez éviter que l'implémentation ne passe dans l'en-tête, cette solution ne résout pas ce problème.template
définitions hors ligne . Je comprends pourquoi les gens veulent le faire - pour atteindre la plus grande parité avec les déclarations / définitions non-modèles, pour garder la déclaration d'interface en ordre, etc. - mais cela ne vaut pas toujours la peine. Il s'agit d'évaluer les compromis des deux côtés et de choisir le moins mauvais . ... jusqu'à cenamespace class
que ça devienne une chose: O [ s'il vous plaît soyez une chose ]Il n'y a rien de mal avec l'exemple que vous avez donné. Mais je dois dire que je pense qu'il n'est pas efficace de stocker des définitions de fonctions dans un fichier cpp. Je comprends seulement la nécessité de séparer la déclaration et la définition de la fonction.
Lorsqu'elle est utilisée avec une instanciation de classe explicite, la bibliothèque de vérification de concept Boost (BCCL) peut vous aider à générer du code de fonction de modèle dans des fichiers cpp.
la source
Rien de ce qui précède n'a fonctionné pour moi, alors voici comment y résolu, ma classe n'a qu'une seule méthode basée sur des modèles ..
.h
.cpp
cela évite les erreurs de l'éditeur de liens, et pas besoin d'appeler du tout TemporaryFunction
la source