Stockage des définitions de fonction de modèle C ++ dans un fichier .CPP

StackOverflow https://stackoverflow.com/questions/115703

  •  02-07-2019
  •  | 
  •  

Question

J'ai quelques modèles de code que je préférerais avoir stockés dans un fichier CPP plutôt que intégrés 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 stratagème 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 utiliser le portage vers d'autres environnements.

Était-ce utile?

La solution

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 vous recommande de lire les points suivants dans la FAQ C ++ Lite :

Ils détaillent beaucoup ces problèmes (et d’autres).

Autres conseils

Pour les autres utilisateurs de 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, & # 167; 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é, d'une fonction de membre non exportée ou d'un membre de données statiques d'un modèle de classe doit figurer dans chaque unité de traduction dans laquelle elle est explicitement instanciée.

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 censé anticiper toutes les utilisations possibles de la classe basées sur des modèles?

Une autre approche est une légère variation de ce que vous avez: ajoutez un troisième fichier qui est le fichier de modèle d'implémentation / instanciation.

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>;

La seule réserve 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 sûr, 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 jeu, 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. Évidemment, cette solution ne sera pas parfaite (alias solution miracle), car vous risqueriez de casser votre ODR:).

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 à ma connaissance, à part Comeau.

Voir le FAQ-lite à ce sujet .

Oui, c'est la méthode standard pour effectuer une instanciation explicite spécialisation . Comme vous l'avez dit, vous ne pouvez pas instancier ce modèle avec d'autres types.

Modifier: 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.

  1. Définissez dans la définition de la classe. Cela ne me plaît pas du tout, car je pense que les définitions de classe sont strictement à titre de référence et doivent être faciles à lire. 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 transforme également le modèle en véritable modèle.

  2. 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.

  3. Incluez l'en-tête.h et l'implémentation.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 c'est que ce n'est pas naturel. Nous n'incluons pas normalement les fichiers source Je suppose que puisque vous avez inclus le fichier source, les fonctions du modèle peuvent être en ligne.

  4. 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 saturé.

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 ne comprends que la nécessité de séparer la déclaration et la définition de la fonction.

Lorsqu'elle est utilisée avec l'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 les fichiers cpp.

Il est temps de faire 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 en ligne après 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.

Prenons un exemple. Supposons pour une raison quelconque que vous souhaitiez 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 générera 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 les deux dernières lignes du 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èle est facultative si 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 maux de tête à l'éditeur de liens, il est donc recommandé d'utiliser l'exemple précédent si vous n'exportez pas la fonction .dll.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top