Fractionner les classes C++ en modèles.php/.dossiers du RPC--est-ce possible?

j'obtiens des erreurs en essayant de compiler une classe de template C++ qui est partagée entre un fichier .hpp et .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<int>::stack()'  
main.cpp:(.text+0x1c): undefined reference to 'stack<int>::~stack()'  
collect2: ld returned 1 exit status  
make: *** [program] Error 1  

Voici mon code:

de la pile.hpp :

#ifndef _STACK_HPP
#define _STACK_HPP

template <typename Type>
class stack {
    public:
            stack();
            ~stack();
};
#endif

de la pile.cpp :

#include <iostream>
#include "stack.hpp"

template <typename Type> stack<Type>::stack() {
        std::cerr << "Hello, stack " << this << "!" << std::endl;
}

template <typename Type> stack<Type>::~stack() {
        std::cerr << "Goodbye, stack " << this << "." << std::endl;
}

principal.cpp :

#include "stack.hpp"

int main() {
    stack<int> s;

    return 0;
}

ld est cours correct: les symboles ne sont pas dans stack.o .

la réponse à La cette question n'aide pas, comme je le fais déjà comme il le dit.

"celui-ci peut aider, mais je ne veux pas déplacer chaque méthode dans le fichier .hpp - Je ne devrais pas avoir à, devrais-je?

est la seule solution raisonnable pour déplacer tout dans le fichier .cpp vers le fichier .hpp , et il suffit d'inclure tout, plutôt que comme un objet autonome fichier? Cela semble horriblement laid! Dans ce cas, je pourrais tout aussi bien revenir à mon état précédent et renommer stack.cpp en stack.hpp et être fait avec elle.

76
demandé sur Community 2009-11-12 20:40:11

16 réponses

il n'est pas possible d'écrire la mise en œuvre d'une classe de modèle dans un fichier cpp séparé et de compiler. Toutes les façons de le faire, si quelqu'un le prétend, sont des solutions de rechange pour imiter l'utilisation de fichiers cpp séparés, mais pratiquement si vous avez l'intention d'écrire une bibliothèque de classe de modèle et de la distribuer avec des fichiers d'en-tête et de lib pour cacher l'implémentation, ce n'est tout simplement pas possible.

pour savoir pourquoi, regardons le processus de compilation. Les fichiers d'en-tête ne sont jamais compilé. Ils sont seulement prétraités. Le code pré-traité est ensuite clubbé avec le fichier cpp qui est en fait compilé. Maintenant, si le compilateur doit générer la mise en page mémoire appropriée pour l'objet, il doit connaître le type de données de la classe template.

en fait, il doit être compris que la classe template n'est pas une classe du tout mais un modèle pour une classe dont la déclaration et la définition est générée par le compilateur au moment de compiler après avoir obtenu les informations de la type de données de l'argument. Tant que la mise en page 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 de classe est l'opérateur 'this'. Toutes les méthodes de classe sont converties en méthodes individuelles avec nom mangling et le premier paramètre comme l'objet sur lequel il opère. L'argument 'this' est celui qui indique effectivement la taille de l'objet qui incase de la classe template n'est pas disponible pour le compilateur à moins que l'utilisateur instanciates l'objet avec un argument de type valide. Dans ce cas, si vous mettez les définitions de méthode dans un fichier cpp séparé et que vous essayez de le compiler, le fichier objet lui-même ne sera pas généré avec les informations de la classe. La compilation n'échouera pas, elle générera le fichier objet mais ne générera aucun code pour la classe template dans le fichier objet. C'est la raison pour laquelle le linker est incapable de trouver les symboles dans les fichiers d'objets et la compilation échoue.

maintenant que est-ce que l'alternative est de cacher les détails importants de la mise en œuvre? Comme nous le savons tous, l'objectif principal de la séparation de l'interface de l'implémentation est de cacher les détails de l'implémentation sous forme binaire. C'est là que vous devez séparer les structures de données et algorithmes. Vos classes de modèles ne doivent représenter que les structures de données et non les algorithmes. Cela vous permet de cacher des détails d'implémentation plus précieux dans des bibliothèques de classes séparées non-templatisées, les classes à l'intérieur desquelles fonctionnerait le modèle classes ou utilisez-les simplement pour contenir des données. La classe template contiendrait en fait moins de code pour assigner, obtenir et définir des données. Le reste du travail serait effectué par les classes d'algorithmes.

j'espère que cette discussion sera utile.

132
répondu Sharjith N. 2012-07-13 17:24:51

It is possible, tant que vous savez de quelles instanciations vous aurez besoin.

ajouter le code suivant à la fin de la pile.rpc et ça marchera :

template class stack<int>;

toutes les méthodes non-template de la pile seront instanciées, et l'étape de lien fonctionnera très bien.

75
répondu Benoît 2014-06-10 19:10:37

Vous pouvez le faire de cette façon

// xyz.h
#ifndef _XYZ_
#define _XYZ_

template <typename XYZTYPE>
class XYZ {
  //Class members declaration
};

#include "xyz.cpp"
#endif

//xyz.cpp
#ifdef _XYZ_
//Class definition goes here

#endif

cela a été discuté dans Daniweb

aussi dans FAQ mais en utilisant le mot-clé export C++.

8
répondu Sadanand 2012-04-03 12:06:41

non, ce n'est pas possible. Pas sans le mot-clé export , qui, à toutes fins pratiques, n'existe pas vraiment.

le mieux que vous pouvez faire est de mettre vos implémentations de fonction dans un".cci" ou ".ppt fichier", et #inclure l' .cci de fichier à la fin de votre .php fichier. Cependant, ceci n'est que superficiel; c'est toujours la même chose que de tout mettre en œuvre dans les fichiers d'en-tête. C'est tout simplement le prix que vous payez pour utiliser les gabarits.

6
répondu Charles Salvia 2009-11-12 17:53:43

je crois qu'il y a deux raisons principales pour tenter de séparer le code templated en un en-tête et un cpp:

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

les Autres c'est la réduction du temps de compilation.

je suis actuellement (comme toujours) logiciel de simulation de codage en conjonction avec OpenCL et nous aimons garder le code afin qu'il puisse être exécuté en utilisant float (cl_float) ou double (cl_double) types selon les besoins en fonction de la capacité HW. Pour l'instant, cela se fait en utilisant un #define REAL au début du code, mais ce n'est pas très élégant. Pour changer la précision désirée, il faut recompiler l'application. Depuis il n'y a pas de réel moment de l'exécution, nous avons à vivre avec cela pour le moment. Heureusement, les noyaux OpenCL sont compilés pour l'exécution, et un simple sizeof (réel) nous permet de modifier l'exécution du code du noyau en conséquence.

le plus gros problème 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) doivent également être modélisées. Ces classes apparaissent toutes au moins une fois au sommet de l'arbre des dépendances de classe, car la Simulation finale de classe de modèle aura une instance d'une de ces classes d'usine, ce qui signifie que pratiquement chaque fois que je fais un changement mineur à la classe d'usine, le logiciel entier doit être reconstruit. C'est très ennuyeux, mais je ne peux pas sembler trouver une meilleure solution.

3
répondu Meteorhead 2012-11-09 09:05:09

parfois, il est possible d'avoir la plupart de l'implémentation cachée dans le fichier cpp, si vous pouvez extraire la fonctionnalité commune de tous les paramètres du modèle dans la classe non-modèle (peut-être type-unsafe). Alors header contiendra des appels de redirection vers cette classe. Une approche similaire est utilisée, lors de la lutte avec le problème "template bloat".

2
répondu Konstantin Tenzin 2009-11-12 18:07:00

si vous savez avec quels types votre pile sera utilisée, Vous pouvez les instancier explicitement dans le fichier cpp, et y conserver tous les codes pertinents.

il est également possible de les exporter à travers des DLLs (!) mais il est assez difficile d'obtenir la bonne syntaxe (combinaisons MS-specific de __declspec(dllexport) et le mot clé d'exportation).

nous avons utilisé cela dans une lib math/geom que Templier double/float, mais avait tout à fait beaucoup de code. (J'ai googlé autour de il n'a pas ce code aujourd'hui.)

2
répondu Macke 2009-11-12 18:30:58

Le problème est qu'un modèle ne génère pas d'une classe réelle, c'est juste un modèle dire au compilateur comment générer une classe. Vous avez besoin de générer une classe concrète.

La manière facile et naturelle est de mettre les méthodes dans le fichier d'en-tête. Mais il existe une autre manière.

dans votre .cpp file, Si vous avez une référence à chaque instanciation et méthode de template que vous avez besoin, le compilateur les générera là pour utiliser tout au long de votre projet.

nouvelle pile.cpp:

#include <iostream>
#include "stack.hpp"
template <typename Type> stack<Type>::stack() {
        std::cerr << "Hello, stack " << this << "!" << std::endl;
}
template <typename Type> stack<Type>::~stack() {
        std::cerr << "Goodbye, stack " << this << "." << std::endl;
}
static void DummyFunc() {
    static stack<int> stack_int;  // generates the constructor and destructor code
    // ... any other method invocations need to go here to produce the method code
}
2
répondu Mark Ransom 2009-11-12 19:00:41

vous devez avoir tout dans le fichier hpp. Le problème est que les classes ne sont pas réellement créées jusqu'à ce que le compilateur voit qu'elles sont nécessaires pour un autre fichier cpp - il doit donc avoir tout le code disponible pour compiler la classe templated à 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-templated (qui peut être divisée entre cpp/hpp) et la partie type-specific template qui hérite du non-templated classe.

1
répondu Aaron 2009-11-12 17:44:16

uniquement si vous #include "stack.cpp à la fin de stack.hpp . Je ne recommande cette approche que si la mise en œuvre est relativement importante, et si vous renommez le .rpc fichier à un autre poste, comme pour le différencier de code standard.

1
répondu lyricat 2009-11-12 17:46:32

C'est une question assez ancienne, mais je pense qu'il est intéressant de regarder la présentation de Arthur O'Dwyer à la cppcon 2016 . Bonne explication, beaucoup de sujet couvert, à surveiller absolument.

1
répondu FreeYourSoul 2017-07-09 10:29:07

parce que les gabarits sont compilés au besoin, cela impose une restriction pour les projets multi-fichiers: la mise en œuvre (définition) d'une classe de gabarits ou d'une fonction doit être 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 séparé, et que nous devons inclure à la fois l'interface et la mise en œuvre dans tout fichier qui utilise les modèles.

0
répondu ChadNC 2009-11-12 17:43:03

une autre possibilité est de faire quelque chose comme:

#ifndef _STACK_HPP
#define _STACK_HPP

template <typename Type>
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 comme une question de style, mais il peut vous convenir.

0
répondu luke 2009-11-12 17:49:04

le mot clé "exporter" permet de séparer la mise en œuvre du modèle de la déclaration du modèle. Ceci a été introduit dans la norme C++ sans une implémentation existante. En temps voulu seulement quelques compilateurs l'ont réellement mis en œuvre. Lire en profondeur l'information à Inform IT article sur l'exportation

0
répondu Shailesh Kumar 2009-11-12 17:51:32

1) rappelez-vous la raison principale de se séparer .h et .les fichiers cpp est de cacher l'implémentation de classe comme un code Obj compilé séparément qui peut être lié au code de l'utilisateur qui a inclus A.h de la classe.

2) Les classes sans modèle ont toutes les variables concrètement et spécifiquement définies dans .h et .fichiers cpp. Ainsi, le compilateur aura besoin d'informations sur tous les types de données utilisés dans la classe avant de compiler/traduire: code Les classes de modèles n'ont aucune information sur le type de données spécifique avant que l'utilisateur de la classe instancie un objet passant le type de données requis:

        TClass<int> myObj;

3) ce N'est qu'après cette instanciation que le compilateur génère la version spécifique de la classe template pour correspondre au(x) type (S) de données passé (s).

4) par conséquent,.le cpp ne peut pas être compilé séparément sans connaître le type de données de l'utilisateur. Il doit donc rester comme code source".h" jusqu'à l'utilisateur de spécifier le type de données requis, il peut être généré à un type de données spécifique, puis compilé

0
répondu Aaron01 2014-04-27 18:39:46

je travaille avec Visual studio 2010, si vous souhaitez diviser vos fichiers .h et .rpc, inclure votre rpc-tête à la fin de l' .h fichier

-3
répondu Ahmad 2013-06-26 14:00:40