111 votes

Diviser les classes C++ modélisées en fichiers .hpp/.cpp - est-ce possible ?

J'obtiens des erreurs en essayant de compiler une classe de modèle C++ qui est divisée entre une classe de modèle et une classe de modèle. .hpp y .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 correcte : les symboles ne sont pas dans stack.o .

La réponse à cette question ne m'aide pas, car je fais déjà ce qui est indiqué.
Celui-là pourrait aider, mais je ne veux pas déplacer toutes les méthodes dans la section .hpp Je ne devrais pas avoir à le faire, n'est-ce pas ?

Est-ce que la seule solution raisonnable est de déplacer tout ce qui est dans le .cpp vers le fichier .hpp et simplement tout inclure, plutôt que de le lier comme un fichier objet autonome ? Cela semble terriblement C'est moche ! Dans ce cas, je pourrais aussi bien revenir à mon état précédent et renommer stack.cpp a stack.hpp et en finir avec ça.

0 votes

Il existe deux excellentes solutions de contournement pour les cas où vous voulez vraiment garder votre code caché (dans un fichier binaire) ou le garder propre. Il est nécessaire de réduire la généralité bien que dans la première situation. Elle est expliquée ici : stackoverflow.com/questions/495021/

1 votes

L'instanciation explicite des modèles est la façon dont vous pouvez réduire le temps de compilation des modèles : stackoverflow.com/questions/2351148/

170voto

Sharjith N. Points 644

Il n'est pas possible d'écrire l'implémentation d'une classe template 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 contournement pour imiter l'utilisation d'un fichier cpp séparé, mais en pratique, si vous avez l'intention d'écrire une bibliothèque de classes template et de la distribuer avec des fichiers header et lib pour cacher l'implémentation, c'est tout simplement impossible.

Pour savoir pourquoi, examinons 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 associé au fichier cpp qui est effectivement compilé. Maintenant, si le compilateur doit générer la disposition mémoire appropriée pour l'objet, il doit connaître le type de données de la classe modèle.

En fait, il faut comprendre que la classe modèle n'est pas du tout une classe mais un modèle de 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 sur le 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. N'oubliez pas 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 une gestion des noms et le premier paramètre est l'objet sur lequel elles opèrent. L'argument 'this' indique la taille de l'objet qui, dans le cas d'une classe modèle, n'est pas disponible pour le compilateur, sauf si l'utilisateur instancie l'objet avec un argument de type valide. Dans ce cas, si vous mettez les définitions de méthodes 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 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 compilation échoue.

Maintenant, quelle est l'alternative pour cacher les détails importants de l'implémentation ? Comme nous le savons tous, l'objectif principal de la séparation de l'interface et de la mise en œuvre est de cacher les détails de la mise en œuvre sous forme binaire. C'est ici que vous devez séparer les structures de données et les algorithmes. Vos classes de modèles doivent représenter uniquement 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 modélisées, les classes à l'intérieur desquelles travailleront sur les classes de modèles ou les utiliseront simplement pour contenir des données. La classe modèle contient en fait moins de code pour assigner, obtenir et définir les données. Le reste du travail serait effectué par les classes d'algorithmes.

J'espère que cette discussion sera utile.

3 votes

"Il faut comprendre qu'une classe modèle n'est pas une classe du tout" - n'est-ce pas l'inverse ? La classe template est un template. "Classe template" est parfois utilisé à la place de "instanciation d'un template", et serait une classe réelle.

0 votes

À titre d'information, il n'est pas correct de dire qu'il n'y a pas de solutions de rechange ! Séparer les structures de données des méthodes est également une mauvaise idée car elle s'oppose à l'encapsulation. Il y a une excellente solution de contournement que vous pouvez utiliser dans certaines situations (la plupart, je crois) ici : stackoverflow.com/questions/495021/

0 votes

@Xupicor, vous avez raison. Techniquement, "Class Template" est ce que vous écrivez pour pouvoir instancier une "Class Template" et son objet correspondant. Cependant, je pense que dans une terminologie générique, utiliser les deux termes de manière interchangeable ne serait pas si mal, la syntaxe pour définir le "Class Template" lui-même commence par le mot "template" et non "class".

102voto

Benoît Points 10901

Il es possible, pour autant que vous sachiez de quelles instanciations vous aurez besoin.

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

template class stack<int>;

Toutes les méthodes non modèles de la pile seront instanciées, et l'étape de liaison fonctionnera bien.

9 votes

En pratique, la plupart des gens utilisent un fichier cpp séparé pour cela - quelque chose comme stackinstantiations.cpp.

0 votes

@NemanjaTrifunovic pouvez-vous donner un exemple de ce à quoi ressemblerait stackinstantiations.cpp ?

3 votes

En fait, il existe d'autres solutions : codeproject.com/Articles/48575/

9voto

Sadanand Points 191

Vous pouvez le faire de la manière suivante

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

Ce sujet a été abordé dans Daniweb

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

5 votes

include d'un cpp est généralement une mauvaise idée. Même si vous avez une raison valable pour cela, le fichier - qui n'est en fait qu'un en-tête glorifié - doit recevoir un numéro d'identification de fichier. hpp ou une autre extension (par exemple tpp ) afin de rendre très clair ce qui se passe, d'éliminer la confusion autour de la makefile s ciblant réel cpp fichiers, etc.

0 votes

@underscore_d Pourriez-vous expliquer pourquoi inclure une .cpp est une idée terrible ?

2 votes

@Abbas parce que l'extension cpp (ou cc o c ou autre) indique que le fichier est un élément 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. Il n'indique pas que le fichier est une partie réutilisable de l'interface, à inclure arbitrairement n'importe où. #include l'ingestion d'un réel cpp remplirait rapidement votre écran avec de multiples erreurs de définition, et ce à juste titre. dans ce cas, comme il y a es une raison de #include il, cpp était juste le mauvais choix d'extension.

4voto

Charles Salvia Points 28661

Non, ce n'est pas possible. Pas sans le export qui, à toutes fins utiles, n'existe pas vraiment.

Le mieux que vous puissiez faire est de mettre vos implémentations de fonctions dans un fichier ".tcc" ou ".tpp", et de #inclure le fichier .tcc à la fin de votre fichier .hpp. Cependant, cela n'est que cosmétique ; c'est toujours la même chose que de tout implémenter dans les fichiers d'en-tête. C'est simplement le prix à payer pour utiliser des modèles.

4 votes

Votre réponse n'est pas correcte. Vous pouvez générer du code à partir d'une classe template dans un fichier cpp, si vous savez quels arguments de template utiliser. Voir ma réponse pour plus d'informations.

2 votes

C'est vrai, mais cela s'accompagne d'une sérieuse restriction : il faut mettre à jour le fichier .cpp et recompiler à chaque fois qu'un nouveau type est introduit qui utilise le modèle, ce qui n'est probablement pas ce que le PO avait à l'esprit.

3voto

Meteorhead Points 133

Je pense qu'il y a deux raisons principales pour essayer de séparer le code modèle en un en-tête et un cpp :

L'un d'eux est pour la simple élégance. Nous aimons tous écrire du code qui soit facile à lire, à gérer et réutilisable par la suite.

L'autre est la réduction des temps de compilation.

Je suis actuellement (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é en utilisant les types float (cl_float) ou double (cl_double) selon les besoins et les capacités du matériel. Actuellement, 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 désirée nécessite de recompiler l'application. Puisqu'il n'existe pas de véritables types d'exécution, nous devons vivre avec cela pour le moment. Heureusement, les noyaux OpenCL sont compilés à l'exécution, et un simple sizeof(REAL) nous permet de modifier le code du noyau à l'exécution en conséquence.

Le problème le plus important est que, même si l'application est modulaire, lors du développement de classes auxiliaires (comme celles qui précalculent les constantes de simulation), il faut également créer des modèles. Ces classes apparaissent toutes au moins une fois au sommet de l'arbre de dépendance des classes, car la classe de modèle final Simulation aura une instance de l'une de ces classes d'usine, ce qui signifie que pratiquement chaque fois que j'apporte une modification mineure à la classe d'usine, le logiciel entier doit être reconstruit. C'est très ennuyeux, mais je ne parviens pas à trouver une meilleure solution.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X