Mon style personnel avec C ++ a toujours mis les déclarations de classe dans un fichier include et les définitions dans un fichier .cpp, tout comme stipulé dans la réponse de Loki aux fichiers d'en-tête C ++, Séparation de code . Certes, une partie de la raison pour laquelle j'aime ce style a probablement à voir avec toutes les années que j'ai passées à coder Modula-2 et Ada, qui ont toutes deux un schéma similaire avec des fichiers de spécifications et des fichiers de corps.
J'ai un collègue, beaucoup plus compétent en C ++ que moi, qui insiste pour que toutes les déclarations C ++ devraient, si possible, inclure les définitions juste là dans le fichier d'en-tête. Il ne dit pas que c'est un style alternatif valide, ou même un style légèrement meilleur, mais c'est plutôt le nouveau style universellement accepté que tout le monde utilise maintenant pour C ++.
Je ne suis pas aussi souple qu'avant, donc je ne suis pas vraiment impatient de grimper dans son train jusqu'à ce que je voie quelques autres personnes là-haut avec lui. Alors, à quel point cet idiome est-il vraiment courant?
Juste pour donner une certaine structure aux réponses: est-ce maintenant The Way , très commun, quelque peu courant, peu commun ou fou?
Réponses:
Votre collègue a tort, la façon la plus courante est et a toujours été de mettre du code dans des fichiers .cpp (ou n'importe quelle extension que vous aimez) et des déclarations dans les en-têtes.
Il est parfois utile de mettre du code dans l'en-tête, cela peut permettre une compilation plus intelligente par le compilateur. Mais en même temps, cela peut détruire vos temps de compilation car tout le code doit être traité à chaque fois qu'il est inclus par le compilateur.
Enfin, il est souvent ennuyeux d'avoir des relations d'objets circulaires (parfois souhaitées) lorsque tout le code est constitué des en-têtes.
En bout de ligne, vous aviez raison, il a tort.
EDIT: J'ai pensé à votre question. Il y a un cas où ce qu'il dit est vrai. modèles. De nombreuses bibliothèques "modernes" plus récentes, telles que boost, font un usage intensif des modèles et sont souvent "en-tête uniquement". Cependant, cela ne doit être fait que lorsque vous utilisez des modèles, car c'est la seule façon de le faire lorsque vous les utilisez.
EDIT: Certaines personnes aimeraient un peu plus de clarification, voici quelques réflexions sur les inconvénients de l'écriture de code "en-tête uniquement":
Si vous effectuez une recherche autour de vous, vous verrez beaucoup de gens essayer de trouver un moyen de réduire les temps de compilation en cas de boost. Par exemple: Comment réduire les temps de compilation avec Boost Asio , qui voit une compilation en 14 secondes d'un seul fichier 1K avec boost inclus. Les 14 ne semblent pas "exploser", mais c'est certainement beaucoup plus long que la normale et peuvent s'additionner assez rapidement. Lorsqu'il s'agit d'un grand projet. Les bibliothèques d'en-tête uniquement affectent les temps de compilation d'une manière tout à fait mesurable. Nous le tolérons simplement parce que le boost est si utile.
De plus, il y a beaucoup de choses qui ne peuvent pas être faites uniquement dans les en-têtes (même boost a des bibliothèques que vous devez lier pour certaines parties telles que les threads, le système de fichiers, etc.). Un exemple principal est que vous ne pouvez pas avoir des objets globaux simples dans les bibliothèques d'en-tête uniquement (sauf si vous avez recours à l'abomination qui est un singleton) car vous rencontrerez plusieurs erreurs de définition. REMARQUE: les variables en ligne de C ++ 17 rendront cet exemple particulier réalisable à l'avenir.
Enfin, lorsque vous utilisez boost comme exemple de code d'en-tête uniquement, un détail énorme est souvent oublié.
Boost est une bibliothèque, pas un code de niveau utilisateur. donc ça ne change pas si souvent. Dans le code utilisateur, si vous mettez tout dans les en-têtes, chaque petite modification vous obligera à recompiler le projet entier. C'est une perte de temps monumentale (et ce n'est pas le cas pour les bibliothèques qui ne changent pas de compilation en compilation). Lorsque vous divisez des éléments entre en-tête / source et mieux encore, utilisez des déclarations avancées pour réduire les inclusions, vous pouvez économiser des heures de recompilation lorsqu'elles sont ajoutées sur une journée.
la source
Le jour où les codeurs C ++ s'accordent sur The Way , les agneaux se coucheront avec les lions, les Palestiniens embrasseront les Israéliens et les chats et les chiens seront autorisés à se marier.
La séparation entre les fichiers .h et .cpp est principalement arbitraire à ce stade, vestige des optimisations du compilateur depuis longtemps. À mon avis, les déclarations appartiennent à l'en-tête et les définitions appartiennent au fichier d'implémentation. Mais c'est juste une habitude, pas une religion.
la source
Le code dans les en-têtes est généralement une mauvaise idée car il force la recompilation de tous les fichiers qui incluent l'en-tête lorsque vous modifiez le code réel plutôt que les déclarations. Cela ralentira également la compilation, car vous devrez analyser le code dans chaque fichier qui inclut l'en-tête.
Une raison pour avoir du code dans les fichiers d'en-tête est qu'il est généralement nécessaire pour que le mot clé inline fonctionne correctement et lors de l'utilisation de modèles instanciés dans d'autres fichiers cpp.
la source
Ce qui pourrait vous informer, collègue, est une notion selon laquelle la plupart du code C ++ devrait être basé sur des modèles pour permettre une utilisation maximale. Et s'il est modélisé, alors tout devra être dans un fichier d'en-tête, afin que le code client puisse le voir et l'instancier. Si c'est assez bon pour Boost et la STL, c'est assez bon pour nous.
Je ne suis pas d'accord avec ce point de vue, mais c'est peut-être d'où il vient.
la source
Je pense que votre collègue est intelligent et vous avez également raison.
Les choses utiles que j'ai trouvées que tout mettre dans les en-têtes est que:
Pas besoin d'écrire et de synchroniser les en-têtes et les sources.
La structure est simple et aucune dépendance circulaire n'oblige le codeur à créer une "meilleure" structure.
Portable, facile à intégrer à un nouveau projet.
Je suis d'accord avec le problème du temps de compilation, mais je pense que nous devrions remarquer que:
Le changement de fichier source est très susceptible de modifier les fichiers d'en-tête, ce qui entraîne la recompilation de l'ensemble du projet.
La vitesse de compilation est beaucoup plus rapide qu'auparavant. Et si vous avez un projet à construire à long terme et à haute fréquence, cela peut indiquer que la conception de votre projet a des défauts. Séparez les tâches en différents projets et le module peut éviter ce problème.
Enfin, je veux juste soutenir votre collègue, juste à mon avis personnel.
la source
Souvent, je mettrai des fonctions membres triviales dans le fichier d'en-tête, pour leur permettre d'être intégrées. Mais y mettre tout le corps du code, juste pour être cohérent avec les modèles? Ce sont des noix simples.
Rappelez-vous: une consistance stupide est le hobgobelin des petits esprits .
la source
A<B>
dans un fichier cpp et que l'utilisateur souhaite unA<C>
?Comme l'a dit Tuomas, votre en-tête doit être minimal. Pour être complet je développerai un peu.
J'utilise personnellement 4 types de fichiers dans mes
C++
projets:De plus, je couple ceci avec une autre règle: ne définissez pas ce que vous pouvez transmettre déclarer. Bien sûr, je suis raisonnable là-bas (utiliser Pimpl partout est assez compliqué).
Cela signifie que je préfère une déclaration
#include
directive dans mes en-têtes chaque fois que je peux m'en sortir.Enfin, j'utilise également une règle de visibilité: je limite le plus possible les portées de mes symboles afin qu'elles ne polluent pas les portées extérieures.
En résumé:
La bouée de sauvetage ici est que la plupart du temps l'en-tête avant est inutile: seulement nécessaire dans le cas de
typedef
outemplate
et est donc l'en-tête de mise en œuvre;)la source
Pour ajouter plus de plaisir, vous pouvez ajouter des
.ipp
fichiers qui contiennent l'implémentation du modèle (qui est inclus dans.hpp
), tandis que.hpp
contient l'interface.Comme en dehors du code modélisé (selon le projet, il peut s'agir d'une majorité ou d'une minorité de fichiers), il existe un code normal et ici, il est préférable de séparer les déclarations et les définitions. Fournissez également des déclarations avancées si nécessaire - cela peut avoir un effet sur le temps de compilation.
la source
Généralement, lors de l'écriture d'une nouvelle classe, je mettrai tout le code dans la classe, donc je n'ai pas besoin de chercher dans un autre fichier. Après que tout fonctionne, je casse le corps des méthodes dans le fichier cpp , laissant les prototypes dans le fichier hpp.
la source
Si cette nouvelle voie est vraiment La Voie , nous aurions peut-être eu une direction différente dans nos projets.
Parce que nous essayons d'éviter toutes les choses inutiles dans les en-têtes. Cela implique d'éviter la cascade d'en-tête. Le code dans les en-têtes aura probablement besoin d'un autre en-tête à inclure, qui aura besoin d'un autre en-tête et ainsi de suite. Si nous sommes obligés d'utiliser des modèles, nous essayons d'éviter de surcharger les en-têtes avec des trucs de modèles.
Nous utilisons également le modèle "pointeur opaque", le cas échéant.
Avec ces pratiques, nous pouvons réaliser des builds plus rapidement que la plupart de nos pairs. Et oui ... changer le code ou les membres de la classe ne provoquera pas de reconstructions énormes.
la source
Personnellement, je fais cela dans mes fichiers d'en-tête:
Je n'aime pas mélanger le code des méthodes avec la classe car je trouve difficile de rechercher rapidement les choses.
Je ne mettrais pas TOUTES les méthodes dans le fichier d'en-tête. Le compilateur ne sera (normalement) pas en mesure d'inline les méthodes virtuelles et ne sera (probablement) que les petites méthodes en ligne sans boucles (dépend totalement du compilateur).
Faire les méthodes dans la classe est valide ... mais d'un point de vue de lisibilité, je n'aime pas ça. Mettre les méthodes dans l'en-tête signifie que, si possible, elles seront intégrées.
la source
À mon humble avis, il n'a de mérite que s'il fait des modèles et / ou une métaprogrammation. Il y a de nombreuses raisons déjà mentionnées pour limiter les fichiers d'en-tête aux seules déclarations. Ce ne sont que ... des en-têtes. Si vous souhaitez inclure du code, vous le compilez en tant que bibliothèque et le liez.
la source
J'ai mis toute l'implémentation hors de la définition de classe. Je veux avoir les commentaires doxygen hors de la définition de classe.
la source
Je pense qu'il est absolument absurde de mettre TOUTES vos définitions de fonctions dans le fichier d'en-tête. Pourquoi? Parce que le fichier d'en-tête est utilisé comme interface PUBLIC pour votre classe. C'est l'extérieur de la "boîte noire".
Lorsque vous devez regarder une classe pour savoir comment l'utiliser, vous devez regarder le fichier d'en-tête. Le fichier d'en-tête doit donner une liste de ce qu'il peut faire (commenté pour décrire les détails de l'utilisation de chaque fonction), et il doit inclure une liste des variables membres. Il NE DEVRAIT PAS inclure COMMENT chaque fonction individuelle est implémentée, car c'est une charge d'informations inutiles et ne fait qu'encombrer le fichier d'en-tête.
la source
Cela ne dépend-il pas vraiment de la complexité du système et des conventions internes?
En ce moment, je travaille sur un simulateur de réseau de neurones qui est incroyablement complexe, et le style accepté que je suis censé utiliser est:
Définitions de classe dans classname.h
Code de classe dans classnameCode.h
Code exécutable dans classname.cpp
Cela sépare les simulations créées par l'utilisateur des classes de base développées par le développeur et fonctionne mieux dans la situation.
Cependant, je serais surpris de voir des gens faire cela dans, disons, une application graphique ou toute autre application dont le but n'est pas de fournir aux utilisateurs une base de code.
la source
Le code du modèle ne doit figurer que dans les en-têtes. En dehors de cela, toutes les définitions, à l'exception des lignes en ligne, doivent être en .cpp. Le meilleur argument pour cela serait les implémentations de la bibliothèque std qui suivent la même règle. Vous ne seriez pas en désaccord avec le fait que les développeurs de std lib auraient raison à ce sujet.
la source
libstdc++
semble (AFAICS) ne mettre presque riensrc
et presque toutinclude
, qu'il soit ou non «en-tête». Je ne pense donc pas que ce soit une citation précise / utile. Quoi qu'il en soit, je ne pense pas que stdlibs soit un modèle de code utilisateur: ils sont évidemment écrits par des codeurs hautement qualifiés, mais pour être utilisés , pas lus: ils résument une complexité élevée à laquelle la plupart des codeurs ne devraient pas avoir à penser , besoin de laid_Reserved
__names
partout pour éviter les conflits avec l'utilisateur, les commentaires et l'espacement sont inférieurs à ce que je conseillerais, etc. Ils sont exemplaires de manière étroite.Je pense que votre collègue a raison tant qu'il n'entre pas dans le processus pour écrire du code exécutable dans l'en-tête. Le bon équilibre, je pense, est de suivre le chemin indiqué par GNAT Ada où le fichier .ads donne une définition d'interface parfaitement adéquate du paquet pour ses utilisateurs et pour ses enfants.
Soit dit en passant, Ted, avez-vous jeté un œil sur ce forum à la récente question sur la liaison Ada à la bibliothèque CLIPS que vous avez écrite il y a plusieurs années et qui n'est plus disponible (les pages Web pertinentes sont maintenant fermées). Même si elle est faite pour une ancienne version de Clips, cette liaison pourrait être un bon exemple de départ pour quelqu'un qui souhaite utiliser le moteur d'inférence CLIPS dans un programme Ada 2012.
la source