J'ai un code de modèle que je préférerais avoir stocké dans un fichier CPP plutôt que 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 est uniquement utilisée avec ints et std :: strings, de sorte que ces définitions signifient que l'application sera liée.
Ma question est la suivante: s’agit-il d’un mauvais bidouillage ou est-ce que cela fonctionnera avec d’autres compilateurs/linkers? Je n'utilise ce code qu'avec VS2008 pour le moment, mais je souhaiterai effectuer un portage vers d'autres environnements.
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 décrite ci-dessus.
Je recommande de lire les points suivants à partir de C++ FAQ Lite :
Ils détaillent beaucoup ces problèmes (et d’autres).
Pour les autres sur cette page qui se demandent quelle est la syntaxe correcte (comme je l’ai fait) pour la spécialisation de modèles explicite (ou du moins dans VS2008), c’est la suivante ...
Dans votre fichier .h ...
template<typename T>
class foo
{
public:
void bar(const T &t);
};
Et dans votre fichier .cpp
template <class T>
void foo<T>::bar(const T &t)
{ }
// Explicit template instantiation
template class foo<int>;
Ce code est bien formé. Vous devez seulement 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 définition d'un modèle de fonction non exporté, d'un modèle de fonction de membre non exporté ou d'une fonction de membre non exportée ou d'un membre de données statique d'un modèle de classe doit figurer dans chaque unité de traduction dans laquelle il est explicitement instancié.
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++.
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 un modèle fasse partie d'une bibliothèque à partager. Faut-il compiler d'autres versions de la classe basée sur un modèle? Le responsable de la bibliothèque est-il supposé anticiper toutes les utilisations possibles 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/instanciation du modèle.
fichier foo.h
// Standard header file guards omitted
template <typename T>
class foo
{
public:
void bar(const T& t);
};
fichier foo.cpp
// Always include your headers
#include "foo.h"
template <typename T>
void foo::bar(const T& t)
{
// Do something with t
}
fichier foo-impl.cpp
// Yes, we include the .cpp file
#include "foo.cpp"
template class foo<int>;
Le seul inconvénient est que vous devez indiquer au compilateur de compiler foo-impl.cpp
au lieu de foo.cpp
, car la compilation de ce dernier ne fait rien.
Bien entendu, vous pouvez avoir plusieurs implémentations dans le troisième fichier ou 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 un modèle pour d'autres utilisations.
Cette configuration réduit également les temps de compilation des classes réutilisées, car vous ne recompilez pas le même fichier d'en-tête dans chaque unité de traduction.
Ce n’est certainement pas un mauvais coup, 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 où BEAUCOUP de types demandent l'instanciation du modèle, il peut y avoir BEAUCOUP de lignes dans votre fichier .cpp. Pour remédier à ce problème, vous pouvez avoir un modèle TemplateClassInst.cpp dans chaque projet que vous utilisez afin de mieux contrôler les types qui seront instanciés. De toute évidence, cette solution ne sera pas parfaite (alias solution miracle), car vous risqueriez de casser l'ODR :).
Il existe, dans la dernière norme, un mot clé (export
) qui permettrait d’atténuer ce problème, mais il n’est implémenté dans aucun compilateur à ma connaissance, à l’exception de Comeau.
Voir le FAQ-lite à ce sujet.
Oui, c'est la façon habituelle de faire spécialisation instanciation explicite. Comme vous l'avez dit, vous ne pouvez pas instancier ce modèle avec d'autres types.
Edit: corrigé en fonction du commentaire.
C'est un moyen standard de définir des fonctions de modèle. Je pense que je lis trois méthodes pour définir des modèles. Ou probablement 4. Chacun avec des avantages et des inconvénients.
Définir dans la définition de classe. Cela ne me plaît pas du tout, car je pense que les définitions de classe sont strictement données à titre indicatif. Cependant, il est beaucoup moins difficile de définir des modèles en classe qu’à l’extérieur. Et toutes les déclarations de modèles ne sont pas au même niveau de complexité. Cette méthode fait également du template un vrai template.
Définissez le modèle dans le même en-tête, mais en dehors de la classe. Ceci est ma manière préférée la plupart du temps. Il garde votre définition de classe ordonnée, le modèle reste un véritable modèle. Cela nécessite toutefois un nommage complet des modèles, ce qui peut être délicat. De plus, votre code est disponible pour tous. Mais si vous avez besoin que votre code soit en ligne, c'est le seul moyen. Vous pouvez également accomplir cela en créant un fichier .INL à la fin de vos définitions de classe.
Incluez les header.h et implementation.CPP dans votre main.CPP. Je pense que c'est comme ça que ça se passe. Vous n'aurez pas à préparer de pré-instanciations, cela se comportera comme un véritable modèle. Le problème que j'ai avec elle est que ce n'est pas naturel. Normalement, nous n'incluons pas et ne comptons pas inclure les fichiers source. Je suppose que puisque vous avez inclus le fichier source, les fonctions du modèle peuvent être en ligne.
Cette dernière méthode, qui était la méthode posté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. Cette méthode ne me pose aucun problème et elle est parfois utile. Nous avons un seul gros code, il ne peut pas tirer avantage d’une mise en ligne; il suffit donc de le placer dans un fichier RPC. 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 petites fonctions régulièrement utilisées dans les fichiers CPP. Cela réduira les performances de votre bibliothèque.
Notez que je ne suis pas au courant des conséquences d'un fichier obj gonflé.
Prenons un exemple, disons pour une raison quelconque que vous souhaitez avoir une classe de modèle:
//test_template.h:
#pragma once
#include <cstdio>
template <class T>
class DemoT
{
public:
void test()
{
printf("ok\n");
}
};
template <>
void DemoT<int>::test()
{
printf("int test (int)\n");
}
template <>
void DemoT<bool>::test()
{
printf("int test (bool)\n");
}
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):
error : multiple definition of `DemoT<int>::test()'; your.o: .../test_template.h:16: first defined here
Il est possible de déplacer l'implémentation vers un fichier .cpp, mais vous devez ensuite déclarer une classe comme ceci -
//test_template.h:
#pragma once
#include <cstdio>
template <class T>
class DemoT
{
public:
void test()
{
printf("ok\n");
}
};
template <>
void DemoT<int>::test();
template <>
void DemoT<bool>::test();
// Instantiate parametrized template classes, implementation resides on .cpp side.
template class DemoT<bool>;
template class DemoT<int>;
Et alors .cpp ressemblera à ceci:
//test_template.cpp:
#include "test_template.h"
template <>
void DemoT<int>::test()
{
printf("int test (int)\n");
}
template <>
void DemoT<bool>::test()
{
printf("int test (bool)\n");
}
Sans deux dernières lignes dans le fichier d'en-tête - gcc fonctionnera correctement, mais Visual studio générera une erreur:
error LNK2019: unresolved external symbol "public: void __cdecl DemoT<int>::test(void)" (?test@?$DemoT@H@@QEAAXXZ) referenced in function
la syntaxe de la classe de modèles est facultative dans le cas où vous souhaitez exposer une fonction via une exportation .dll, mais cela ne s'applique que pour la plate-forme Windows. Par conséquent, test_template.h pourrait ressembler à ceci:
//test_template.h:
#pragma once
#include <cstdio>
template <class T>
class DemoT
{
public:
void test()
{
printf("ok\n");
}
};
#ifdef _WIN32
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT
#endif
template <>
void DLL_EXPORT DemoT<int>::test();
template <>
void DLL_EXPORT DemoT<bool>::test();
avec le fichier .cpp de l'exemple précédent.
Cela donne toutefois plus de problèmes à l'éditeur de liens, il est donc recommandé d'utiliser l'exemple précédent si vous n'exportez pas la fonction .dll.
L'exemple que vous avez donné n'a rien d'anormal. Mais je dois dire que je pense qu’il n’est pas efficace de stocker les définitions de fonctions dans un fichier cpp. Je comprends seulement la nécessité de séparer la déclaration de la fonction et sa définition.
Lorsqu'il est utilisé avec une instanciation de classe explicite, la bibliothèque de contrôle de concept Boost (BCCL) peut vous aider à générer du code de fonction de modèle dans des fichiers cpp.
Temps pour une mise à jour! Créez un fichier en ligne (.inl, ou probablement n’importe quel autre) et copiez simplement toutes vos définitions. 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 l'inverse. Incluez le fichier inline after la déclaration de votre classe (#include "file.inl"
).
Je ne sais pas vraiment pourquoi personne n'en a parlé. Je ne vois aucun inconvénient immédiat.