Je reçois des erreurs en essayant de compiler une classe de modèle C ++ qui est divisée entre un .hpp
et un .cpp
fichier:
$ 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:
stack.hpp :
#ifndef _STACK_HPP
#define _STACK_HPP
template <typename Type>
class stack {
public:
stack();
~stack();
};
#endif
stack.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;
}
main.cpp :
#include "stack.hpp"
int main() {
stack<int> s;
return 0;
}
ld
est bien sûr correct: les symboles ne sont pas inclus stack.o
.
La réponse à cette question n'aide pas, comme je fais déjà ce qu'elle dit.
Celui-ci pourrait aider, mais je ne veux pas déplacer chaque méthode dans le .hpp
fichier - je ne devrais pas avoir à le faire, n'est-ce pas?
La seule solution raisonnable est-elle de déplacer tout ce qui se trouve dans le .cpp
fichier vers le .hpp
fichier, et de tout inclure simplement, plutôt que de créer un lien en tant que fichier objet autonome? Cela semble terriblement moche! Dans ce cas, je pourrais aussi bien revenir à mon état précédent et renommage stack.cpp
à stack.hpp
et faire avec elle.
Réponses:
Il n'est pas possible d'écrire l'implémentation d'une classe de modèle dans un fichier cpp séparé et de la compiler. Toutes les façons de le faire, si quelqu'un le prétend, sont des solutions de contournement pour imiter l'utilisation d'un fichier cpp séparé, mais pratiquement si vous avez l'intention d'écrire une bibliothèque de classes de modèles et de la distribuer avec des fichiers d'en-tête et de bibliothèque pour masquer 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és. Ils ne sont que 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'objet, il doit connaître le type de données de la classe de modèle.
En fait, il faut comprendre que la classe modèle n'est pas du tout 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 à partir de l'argument. Tant que la disposition de la mémoire ne peut pas être créée, les instructions pour la définition de méthode ne peuvent pas être générées. Souvenez-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 le nom mangling et le premier paramètre comme objet sur lequel il opère. L'argument «this» indique en fait la taille de l'objet qui, en cas de classe de modèle, n'est pas disponible pour le compilateur à moins que l'utilisateur instancie l'objet avec un argument de type valide. Dans ce cas, si vous placez les définitions de méthode dans un fichier cpp séparé et essayez de le compiler, le fichier objet lui-même ne sera pas généré avec les informations de classe. La compilation n'échouera pas, elle générera le fichier objet mais elle ne générera aucun code pour la classe modèle dans le fichier objet. C'est la raison pour laquelle l'éditeur de liens est incapable de trouver les symboles dans les fichiers objets et la génération échoue.
Maintenant, quelle est l'alternative pour masquer les détails d'implémentation importants? Comme nous le savons tous, l'objectif principal derrière 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 les algorithmes. Vos classes de modèles doivent représenter uniquement des structures de données et non les algorithmes. Cela vous permet de masquer des détails d'implémentation plus précieux dans des bibliothèques de classes distinctes non modélisées, les classes à l'intérieur qui fonctionneraient sur les classes de modèles ou simplement les utiliser pour contenir des données. La classe de modèle contiendrait en fait moins de code pour affecter, 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.
la source
Il est possible, aussi longtemps que vous savez ce que vous allez Instantiations besoin.
Ajoutez le code suivant à la fin de stack.cpp et cela fonctionnera:
Toutes les méthodes non-modèle de pile seront instanciées et l'étape de liaison fonctionnera correctement.
la source
template class stack<int>;
.Vous pouvez le faire de cette façon
Cela a été discuté dans Daniweb
Aussi dans la FAQ mais en utilisant le mot clé d'exportation C ++.
la source
include
uncpp
fichier est généralement une mauvaise idée. même si vous avez une raison valable pour cela, le fichier - qui n'est en réalité qu'un en-tête glorifié - devrait recevoir unehpp
extension ou une extension différente (par exempletpp
) pour clarifier ce qui se passe, éliminer la confusion autour dumakefile
ciblage descpp
fichiers réels , etc..cpp
fichier est une mauvaise idée?cpp
(oucc
, ouc
, ou autre) indique que le fichier est une partie de l'implémentation, que l'unité de traduction résultante (sortie du préprocesseur) est compilable séparément et que le contenu du fichier n'est compilé qu'une seule fois. cela n'indique pas que le fichier est une partie réutilisable de l'interface, à inclure arbitrairement n'importe où.#include
un fichier réelcpp
remplirait rapidement votre écran de multiples erreurs de définition, et à juste titre. dans ce cas, comme il y a une raison à#include
cela,cpp
c'était juste le mauvais choix d'extension..cpp
extension pour une telle utilisation. Mais utiliser un autre mot à dire.tpp
est tout à fait correct, qui servirait le même but mais utiliserait une extension différente pour une compréhension plus facile / plus rapide?cpp
/cc
/ etc doivent être évités, mais il est une bonne idée d'utiliser autre chose quehpp
- par exempletpp
,tcc
etc. - afin que vous puissiez réutiliser le reste du nom de fichier et indiquer que letpp
fichier, bien qu'il agit comme un en- tête, contient l'implémentation hors ligne des déclarations de modèle dans le fichierhpp
. Donc, cet article commence par une bonne prémisse - séparer les déclarations et les définitions en 2 fichiers différents, ce qui peut être plus facile à grok / grep ou parfois est nécessaire en raison des dépendances circulaires IME - mais se termine mal en suggérant que le 2ème fichier a une mauvaise extensionNon, ce n'est pas possible. Pas sans le
export
mot - clé, qui à toutes fins utiles n'existe pas vraiment.Le mieux que vous puissiez faire est de placer vos implémentations de fonction dans un fichier «.tcc» ou «.tpp», et #incluez le fichier .tcc à la fin de votre fichier .hpp. Cependant, ce n'est que cosmétique; c'est toujours la même chose que l'implémentation de tout dans les fichiers d'en-tête. C'est simplement le prix que vous payez pour utiliser des modèles.
la source
Je pense qu'il y a deux raisons principales pour essayer de séparer le code basé sur un modèle en un en-tête et un cpp:
L'un est pour la simple élégance. Nous aimons tous écrire du code qui est difficile à lire, à gérer et qui est réutilisable plus tard.
L'autre est la réduction des temps de compilation.
Je suis actuellement (comme toujours) un logiciel de simulation de codage en conjonction avec OpenCL et nous aimons conserver le code afin qu'il puisse être exécuté en utilisant des types float (cl_float) ou double (cl_double) selon les besoins en fonction de la capacité HW. Pour le moment, cela se fait en utilisant un #define REAL au début du code, mais ce n'est pas très élégant. La modification de la précision souhaitée nécessite la recompilation de l'application. Puisqu'il n'y a pas de vrais types d'exécution, nous devons vivre avec cela pour le moment. Heureusement, les noyaux OpenCL sont compilés à l'exécution, et une simple sizeof (REAL) nous permet de modifier le temps d'exécution du code du noyau 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) doivent également être modélisées. Ces classes apparaissent toutes au moins une fois en haut de l'arborescence des dépendances de classe, car la classe de modèle finale Simulation aura une instance de l'une de ces classes d'usine, ce qui signifie que pratiquement chaque fois que j'apporte un changement mineur à la classe d'usine, l'ensemble le logiciel doit être reconstruit. C'est très ennuyeux, mais je n'arrive pas à trouver une meilleure solution.
la source
Seulement si vous
#include "stack.cpp
à la fin destack.hpp
. Je ne recommanderais cette approche que si l'implémentation est relativement grande et si vous renommez le fichier .cpp en une autre extension, afin de le différencier du code normal.la source
cpp
(cc
ou quoi que ce soit) car c'est un contraste frappant avec son rôle réel. Il devrait plutôt recevoir une extension différente qui indique que c'est (A) un en-tête et (B) un en-tête à inclure au bas d'un autre en-tête. Je l' utilisetpp
pour cela, qui peut également haut la main pour set
emp
fin imp
lementation (définitions hors ligne). J'ai parlé plus à ce sujet ici: stackoverflow.com/questions/1724036/…Parfois, il est possible d'avoir la plupart de l'implémentation cachée dans le fichier cpp, si vous pouvez extraire les fonctionnalités communes de tous les paramètres de modèle dans une classe non-modèle (éventuellement de type non sécurisé). L'en-tête contiendra alors les appels de redirection vers cette classe. Une approche similaire est utilisée lors de la lutte contre le problème de "gonflement du modèle".
la source
Si vous savez avec quels types votre pile sera utilisée, vous pouvez les instancier explicitement dans le fichier cpp et y conserver tout le code pertinent.
Il est également possible de les exporter à travers des DLL (!) Mais il est assez difficile d'obtenir la bonne syntaxe (combinaisons spécifiques à MS de __declspec (dllexport) et du mot-clé d'exportation).
Nous l'avons utilisé dans une bibliothèque mathématique / geom qui avait un modèle double / float, mais qui contenait beaucoup de code. (Je l'ai cherché sur Google à l'époque, je n'ai pas ce code aujourd'hui.)
la source
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.
La manière simple et naturelle est de placer 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 et méthode de modèle dont vous avez besoin, le compilateur les générera là-bas pour une utilisation tout au long de votre projet.
new stack.cpp:
la source
Vous devez avoir tout dans le fichier hpp. Le problème est que les classes ne sont réellement créées que lorsque le compilateur voit qu'elles sont nécessaires à un AUTRE fichier cpp - il doit donc avoir tout le code disponible pour compiler la classe basée sur un modèle à 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 un modèle (qui peut être divisée entre cpp / hpp) et la partie modèle spécifique au type qui hérite de la classe non basée sur un modèle.
la source
Étant donné que les modèles sont compilés au besoin, cela force une restriction pour les projets multi-fichiers: l'implémentation (définition) d'une classe ou d'une fonction de modèle 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 l'implémentation dans tout fichier qui utilise les modèles.
la source
Une autre possibilité est de faire quelque chose comme:
Je n'aime pas cette suggestion pour une question de style, mais elle peut vous convenir.
la source
cpp
pour éviter toute confusion avec les fichiers source réels . Les suggestions courantes incluenttpp
ettcc
.Le mot clé 'export' est le moyen de séparer l'implémentation du modèle de la déclaration du modèle. Cela a été introduit dans la norme C ++ sans implémentation existante. En temps voulu, seuls quelques compilateurs l'ont mis en œuvre. Lisez des informations détaillées dans l' article Inform IT sur l'exportation
la source
1) Rappelez-vous que la raison principale pour séparer les fichiers .h et .cpp est de masquer l'implémentation de la classe en tant que code Obj compilé séparément qui peut être lié au code de l'utilisateur contenant un .h de la classe.
2) Les classes non-modèles 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 compiler / traduire générer l'objet / code machine Les classes de modèle n'ont aucune information sur le type de données spécifique avant que l'utilisateur de la classe instancie un objet en passant les données requises type:
3) Ce n'est qu'après cette instanciation que le complicateur génère la version spécifique de la classe de modèle pour correspondre au (x) type (s) 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 à l'utilisateur. Il doit donc rester en tant que code source dans «.h» jusqu'à ce que l'utilisateur spécifie le type de données requis, puis il peut être généré dans un type de données spécifique puis compilé
la source
L'endroit où vous voudrez peut-être faire cela est lorsque vous créez une combinaison de bibliothèque et d'en-tête et que vous masquez l'implémentation à l'utilisateur. Par conséquent, l'approche suggérée consiste à utiliser une instanciation explicite, car vous savez ce que votre logiciel est censé fournir et vous pouvez masquer les implémentations.
Quelques informations utiles sont ici: https://docs.microsoft.com/en-us/cpp/cpp/explicit-instantiation?view=vs-2019
Pour votre même exemple: Stack.hpp
stack.cpp
main.cpp
Production:
Cependant, je n'aime pas entièrement cette approche, car cela permet à l'application de se tirer une balle dans le pied, en passant des types de données incorrects à la classe modèle. Par exemple, dans la fonction main, vous pouvez passer d'autres types qui peuvent être implicitement convertis en int comme s.Push (1.2); et c'est tout simplement mauvais à mon avis.
la source
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
la source