Diviser les classes C ++ basées sur des modèles en fichiers .hpp / .cpp – est-ce possible?

Je reçois des erreurs en essayant de comstackr une classe de modèle C ++ qui est divisée entre un fichier .hpp et un fichier .cpp :

 $ g++ -c -o main.o main.cpp $ g++ -c -o stack.o stack.cpp $ g++ -o main main.o stack.o main.o: In function `main': main.cpp:(.text+0xe): undefined reference to 'stack::stack()' main.cpp:(.text+0x1c): undefined reference to 'stack::~stack()' collect2: ld returned 1 exit status make: *** [program] Error 1 

Voici mon code:

stack.hpp :

 #ifndef _STACK_HPP #define _STACK_HPP template  class stack { public: stack(); ~stack(); }; #endif 

stack.cpp :

 #include  #include "stack.hpp" template  stack::stack() { std::cerr << "Hello, stack " << this << "!" << std::endl; } template  stack::~stack() { std::cerr << "Goodbye, stack " << this << "." << std::endl; } 

main.cpp :

 #include "stack.hpp" int main() { stack s; return 0; } 

ld est bien sûr correct: les symboles ne sont pas dans stack.o .

La réponse à cette question n’aide pas, comme je le fais déjà comme il est dit.
Celui-ci pourrait aider, mais je ne veux pas déplacer chaque méthode dans le fichier .hpp – je n’aurais pas dû le faire, devrais-je?

Est-ce que la seule solution raisonnable pour tout déplacer dans le fichier .cpp vers le fichier .hpp , et simplement inclure tout, plutôt que de .hpp fichier object autonome? Cela semble terriblement moche! Dans ce cas, je pourrais aussi bien revenir à mon état précédent et renommer stack.cpp en stack.hpp et en stack.hpp avec lui.

Il n’est pas possible d’écrire l’implémentation d’une classe de modèle dans un fichier cpp distinct et de comstackr. Toutes les façons de le faire, si quelqu’un le prétend, sont des solutions pour imiter l’utilisation d’un fichier cpp distinct, mais pratiquement si vous avez l’intention d’écrire une bibliothèque de classes de modèle et de la dissortingbuer avec des fichiers en-tête et lib pour masquer l’implémentation .

Pour savoir pourquoi, regardons le processus de compilation. Les fichiers d’en-tête ne sont jamais compilés. Ils sont seulement prétraités. Le code prétraité est ensuite matraqué avec le fichier cpp qui est réellement compilé. Maintenant, si le compilateur doit générer la disposition de mémoire appropriée pour l’object, il doit connaître le type de données de la classe de modèle.

En fait, il faut comprendre que la classe template n’est pas une classe mais un modèle pour une classe dont la déclaration et la définition sont générées par le compilateur au moment de la compilation après avoir obtenu les informations du type de données de l’argument. Tant que la disposition de la mémoire ne peut pas être créée, les instructions pour la définition de la méthode ne peuvent pas être générées. Rappelez-vous que le premier argument de la méthode class est l’opérateur «this». Toutes les méthodes de classe sont converties en méthodes individuelles avec le nom mangling et le premier paramètre comme object sur lequel il opère. L’argument ‘this’ est ce qui en fait indique la taille de l’object qui, en l’absence de la classe de modèle, n’est pas disponible pour le compilateur, sauf si l’utilisateur instancie l’object avec un argument de type valide. Dans ce cas, si vous placez les définitions de méthode dans un fichier cpp distinct et que vous essayez de le comstackr, le fichier object lui-même ne sera pas généré avec les informations de classe. La compilation n’échouera pas, elle générerait le fichier object mais ne générerait aucun code pour la classe de modèle dans le fichier object. C’est la raison pour laquelle l’éditeur de liens ne parvient pas à trouver les symboles dans les fichiers objects et la génération échoue.

Maintenant, quelle est l’alternative pour cacher les détails importants de la mise en œuvre? Comme nous le soaps tous, l’objective principal de la séparation de l’interface de la mise en œuvre est de cacher les détails de la mise en œuvre sous forme binary. C’est ici que vous devez séparer les structures de données et les algorithmes. Vos classes de modèle ne doivent représenter que des structures de données et non des algorithmes. Cela vous permet de masquer des détails d’implémentation plus précieux dans des bibliothèques de classes non modélisées distinctes, les classes à l’intérieur desquelles fonctionneraient les classes de modèles ou les utiliseraient simplement pour stocker des données. La classe de modèle contiendrait en réalité moins de code pour atsortingbuer, obtenir et définir des données. Le rest du travail serait fait par les classes d’algorithme.

J’espère que cette discussion sera utile.

C’est possible, tant que vous savez quelles instanciations vous allez avoir besoin.

Ajoutez le code suivant à la fin de stack.cpp et cela fonctionnera:

 template class stack; 

Toutes les méthodes non-template de la stack seront instanciées et l’étape de liaison fonctionnera correctement.

Vous pouvez le faire de cette façon

 // xyz.h #ifndef _XYZ_ #define _XYZ_ template  class XYZ { //Class members declaration }; #include "xyz.cpp" #endif //xyz.cpp #ifdef _XYZ_ //Class definition goes here #endif 

Cela a été discuté dans Daniweb

Également dans la FAQ, mais en utilisant le mot-clé d’exportation C ++.

Non, ce n’est pas possible. Non sans le mot-clé d’ export , qui n’existe pas vraiment.

Le mieux que vous puissiez faire est de placer vos implémentations de fonctions dans un fichier “.tcc” ou “.tpp” et d’inclure le fichier .tcc à la fin de votre fichier .hpp. Cependant, il ne s’agit que de produits cosmétiques. c’est toujours la même chose que d’implémenter tout dans les fichiers d’en-tête. C’est simplement le prix que vous payez pour utiliser des modèles.

Je crois qu’il y a deux raisons principales pour essayer de séparer le code basé sur un modèle dans un en-tête et un cpp:

On est pour la simple élégance. Nous aimons tous écrire du code qui est difficile à lire, à gérer et réutilisable plus tard.

Autre est la réduction des temps de compilation.

Je suis (comme toujours) en train de coder un logiciel de simulation en conjonction avec OpenCL et nous aimons garder le code pour qu’il puisse être exécuté avec les types float (cl_float) ou double (cl_double) en fonction de la capacité matérielle. En ce moment, cela se fait en utilisant un #define REAL au début du code, mais ce n’est pas très élégant. Changer la précision souhaitée nécessite de recomstackr l’application. Comme il n’y a pas de vrais types d’exécution, nous devons vivre avec cela pour le moment. Heureusement, les kernelx OpenCL sont des runtime compilés et une simple tailleof (REAL) nous permet de modifier le code du kernel en conséquence.

Le problème beaucoup plus important est que, même si l’application est modulaire, lors du développement de classes auxiliaires (telles que celles qui pré-calculent les constantes de simulation), il faut aussi créer des modèles. Ces classes apparaissent toutes au moins une fois en haut de l’arbre des dépendances de classes, car la dernière classe de simulation Simulation aura une instance de l’une de ces classes, ce qui signifie que chaque fois que je modifie légèrement la classe d’usine, la le logiciel doit être reconstruit. C’est très ennuyant, mais je n’arrive pas à trouver une meilleure solution.

Parfois, il est possible que la plupart des implémentations soient cachées dans un fichier cpp, si vous pouvez extraire des fonctionnalités courantes, rassemblez tous les parameters du modèle dans une classe autre que le modèle (probablement de type non sécurisé). Ensuite, l’en-tête contiendra les appels de redirection vers cette classe. Une approche similaire est utilisée lorsque l’on se bat avec le problème du “gabarit de gabarit”.

Si vous connaissez les types avec lesquels votre stack sera utilisée, vous pouvez les instancier de manière expresse dans le fichier cpp et y conserver tout le code pertinent.

Il est également possible d’exporter ceux-ci sur des DLL (!), Mais il est difficile d’obtenir la syntaxe correcte (combinaisons spécifiques à MS de __declspec (dllexport) et du mot clé export).

Nous l’avons utilisé dans une librairie math / geom basée sur un modèle double / float, mais avec beaucoup de code. (Je l’ai cherché sur Google à l’époque, mais je n’ai pas ce code aujourd’hui.)

Le problème est qu’un modèle ne génère pas de classe réelle, c’est juste un modèle indiquant au compilateur comment générer une classe. Vous devez générer une classe concrète.

Le moyen facile et naturel est de mettre les méthodes dans le fichier d’en-tête. Mais il y a un autre chemin.

Dans votre fichier .cpp, si vous avez une référence à chaque instanciation de modèle et à la méthode dont vous avez besoin, le compilateur les générera pour les utiliser tout au long de votre projet.

new stack.cpp:

 #include  #include "stack.hpp" template  stack::stack() { std::cerr << "Hello, stack " << this << "!" << std::endl; } template  stack::~stack() { std::cerr << "Goodbye, stack " << this << "." << std::endl; } static void DummyFunc() { static stack stack_int; // generates the constructor and destructor code // ... any other method invocations need to go here to produce the method code } 

Vous devez tout avoir dans le fichier hpp. Le problème est que les classes ne sont pas réellement créées tant que le compilateur ne voit pas qu’elles sont nécessaires à un fichier OTHER cpp. Il doit donc avoir tout le code disponible pour comstackr la classe basée sur les modèles à ce moment-là.

Une chose que j’ai tendance à faire est d’essayer de diviser mes modèles en une partie générique non basée sur des modèles (qui peut être divisée entre cpp / hpp) et la partie de modèle spécifique à un type qui hérite de la classe non basée sur un modèle.

Seulement si vous #include "stack.cpp à la fin de stack.hpp . Je recommande cette approche uniquement si l’implémentation est relativement grande et si vous renommez le fichier .cpp en une autre afin de le différencier du code normal. .

C’est une question assez ancienne, mais je pense qu’il est intéressant de regarder la présentation d’ Arthur O’Dwyer au cppcon 2016 . Bonne explication, beaucoup de sujet couvert, une montre à ne pas manquer.

Comme les modèles sont compilés en cas de besoin, cela impose une ressortingction pour les projets multi-fichiers: l’implémentation (définition) d’une classe ou d’une fonction de modèle doit se trouver dans le même fichier que sa déclaration. Cela signifie que nous ne pouvons pas séparer l’interface dans un fichier d’en-tête distinct et que nous devons inclure à la fois l’interface et l’implémentation dans tout fichier utilisant les modèles.

Une autre possibilité est de faire quelque chose comme:

 #ifndef _STACK_HPP #define _STACK_HPP template  class stack { public: stack(); ~stack(); }; #include "stack.cpp" // Note the include. The inclusion // of stack.h in stack.cpp must be // removed to avoid a circular include. #endif 

Je n’aime pas cette suggestion par souci de style, mais cela peut vous convenir.

Le mot clé ‘export’ permet de séparer la mise en œuvre du modèle de la déclaration de modèle. Cela a été introduit dans le standard C ++ sans implémentation existante. En temps voulu, seuls quelques compilateurs l’ont effectivement mis en œuvre. Lire des informations détaillées sur l’article d’Inform IT sur l’exportation

1) N’oubliez pas que la principale raison de séparer les fichiers .h et .cpp est de cacher l’implémentation de la classe en tant que code Obj compilé séparément pouvant être lié au code de l’utilisateur qui inclut un .h de la classe.

2) Les classes non-template ont toutes les variables définies concrètement et spécifiquement dans les fichiers .h et .cpp. Ainsi, le compilateur aura besoin d’informations sur tous les types de données utilisés dans la classe avant de comstackr / traduire  générer le code object / machine Les classes de modèle n’ont aucune information sur le type de données spécifique avant que l’utilisateur type:

  TClass myObj; 

3) Seulement après cette instanciation, le compilateur génère la version spécifique de la classe de modèle pour correspondre aux types de données transmis.

4) Par conséquent, .cpp NE peut PAS être compilé séparément sans connaître le type de données spécifique des utilisateurs. Il doit donc restr en tant que code source dans «.h» jusqu’à ce que l’utilisateur spécifie le type de données requirejs, puis il peut être généré dans un type de données spécifique, puis compilé

Je travaille avec Visual Studio 2010, si vous souhaitez diviser vos fichiers en .h et .cpp, incluez votre en-tête cpp à la fin du fichier .h