Pour & Cons de mettre tout le code dans les fichiers d'en-Tête dans le C++?
Vous pouvez la structure d'un programme C++, de sorte que (presque) tout le code réside dans les fichiers d'en-Tête. Essentiellement, il ressemble à un C# ou Java. Cependant, vous avez besoin d'au moins un .cpp
fichier à tirer dans tous les fichiers d'en-tête lors de la compilation. Maintenant, je sais que certaines personnes seraient absolument horreur de cette idée. Mais je n'ai pas trouvé de convaincre les inconvénients de cette situation. Je peux énumérer quelques avantages:
[1] plus Rapide temps de compilation. Tous les fichiers d'en-tête seulement obtenir analysée une fois, parce qu'il n'y en a qu'une .fichier cpp. Aussi, un fichier d'en-tête ne peut pas être inclus plus d'une fois, sinon vous obtiendrez un build pause. Il y a d'autres moyens d'atteindre plus rapidement compile lorsque vous utilisez l'autre, mais ce n'est si simple.
[2], Il évite les dépendances circulaires, pour le rendre absolument clair. Si ClassA
dans ClassA.h
a une dépendance circulaire sur ClassB
dans ClassB.h
, je dois mettre une référence vers l'avant & ça colle. (Notez que c'est à la différence de C# & Java où le compilateur résout automatiquement les dépendances circulaires. Cela encourage les mauvaises pratiques de codage de l'OMI). Encore une fois, vous pouvez éviter des dépendances circulaires si votre code a été dans .cpp
fichiers, mais dans un monde réel projet, .cpp
fichiers ont tendance à inclure des en-têtes jusqu'à ce que vous ne pouvez pas comprendre qui dépend pour qui.
Vos pensées?
- Si vous en faites une politique à long terme déclare autant que possible. Pas seulement un projet composé de beaucoup de .fichier cpp compiler plus vite, mais que vous n'avez jamais à vous soucier des dépendances circulaires. En gros, si vous n'avez pas besoin de toute la définition de l'en-tête, utiliser un déclarer avant.
- +1 C'est une bonne question, car cette pratique est encore utilisé dans certains gros projets. (www.ogre3D.com par ex.)
- Point 1 ignore la perspective de l'utilisation de plus d'un noyau/machine pour compiler votre projet. La distribution des compilations de plusieurs fichiers cpp multiples cœurs peut battre la compilation du programme comme un seul fichier cpp être compilé sur un seul core.
- Quid des performances ? Ne peut pas le compilateur prendre de meilleures optimisations si elle voit tout le code en une seule fois ? (par exemple, l'in-lining, etc..)
Vous devez vous connecter pour publier un commentaire.
Raison [1] plus Rapide temps de compilation
Pas dans mes projets: les fichiers source (RPC) ne comprennent que les en-têtes (HPP) dont ils ont besoin. Donc quand j'ai besoin de recompiler un seul RPC en raison d'un changement minuscule, j'ai dix fois le même nombre de fichiers qui ne sont pas recompilés.
Vous devriez peut-être briser votre projet plus logique sources/en-têtes: Une modification dans la classe A de la mise en œuvre ne devrait PAS avoir besoin de la recompilation des implémentations de classe B, C, D, E, etc..
Raison[2]. Il évite les dépendances circulaires
Dépendances circulaires dans le code?
Désolé, mais je n'ai pas encore eu ce genre de problème à un problème réel: disons que A dépend de B, et B dépend de A:
Une bonne façon de résoudre le problème serait de briser cette source dans au moins une source de/en-tête de chaque classe (de manière similaire à Java, mais avec une seule source et un seul en-tête de chaque classe):
.
.
.
Donc, pas de problème de dépendance, et encore rapide temps de compilation.
Ai-je raté quelque chose?
Lorsque l'on travaille sur le "monde réel" des projets
De cours. Mais alors, si vous avez le temps de réorganiser les fichiers pour construire votre "un RPC" solution, alors vous avez le temps de nettoyer les têtes. Mes règles pour les en-têtes sont:
De toute façon, tous les en-têtes doivent être autonomes, ce qui signifie:
Cela permettra d'éliminer la commande de problèmes et les dépendances circulaires.
Est temps de compilation d'un problème? Alors...
Devrait moment de la compilation être vraiment un problème, je voudrais envisager:
Conclusion
Ce que vous faites est de ne pas tout mettre dans les en-têtes.
Vous êtes fondamentalement, y compris tous vos fichiers en une seule et unique source final.
Peut-être que vous êtes en train de gagner dans des conditions de pleine-projet de compilation.
Mais lors de la compilation pour un petit changement, vous aurez toujours perdre.
Lors du codage, je sais que je compile souvent de petits changements (si ce n'est que le compilateur valider mon code), puis un dernier temps, faire un changement de projet.
Je perdrais beaucoup de temps si mon projet a été organisée à votre façon.
Je suis en désaccord avec le point 1.
Oui, il n'y en a qu'une .le rpc et le construit du temps à partir de zéro est plus rapide. Mais, il est rare de construire à partir de zéro. Vous faites de petits changements, et il aurait besoin de recompiler l'ensemble du projet à chaque fois.
Je préfère le faire dans l'autre sens:
Ainsi, certains de mes .fichiers cpp commencer à regarder comme le Java ou le C# code 😉
Mais, 'garder les choses en .h' démarche est la bonne, tandis que la conception du système, parce que du point 2. que vous avez fait. J'ai l'habitude de le faire pendant que je suis en train de construire la hiérarchie de classe et, plus tard, lorsque le code de l'architecture devient stable, j'ai passer le code de .fichiers cpp.
Vous avez raison de dire que votre solution fonctionne. Il peut-être même pas les inconvénients pour votre projet actuel et de l'environnement de développement.
Mais...
Que d'autres ont dit, mettre votre code dans les fichiers d'en-tête des forces d'une compilation complète chaque fois que vous changer une ligne de code. Cela peut ne pas être un problème, mais encore votre projet peut devenir suffisamment gros pour le point de temps de compilation sera un problème.
Un autre problème, c'est quand le partage de code. Alors que vous pourriez ne pas être directement concerné pourtant, il est important de garder un maximum de code caché provenant d'un utilisateur potentiel de votre code. En mettant votre code dans le fichier d'en-tête, n'importe quel programmeur à l'aide de votre code doit regarder l'ensemble du code, alors que les intéressés à la façon de l'utiliser. Mettre votre code dans le fichier cpp permet de ne livrer un composant binaire (statique ou dynamique de la bibliothèque) et de son interface que les fichiers d'en-tête, qui peut être plus simple dans certains environnement.
C'est un problème si vous voulez être en mesure de transformer votre code actuel dans une bibliothèque dynamique. Parce que vous n'avez pas une bonne déclaration d'interface découplé du code actuel, vous ne serez pas en mesure de fournir un compilé dynamique de la bibliothèque et son utilisation de l'interface lisible fichiers d'en-tête.
Vous ne pouvez pas avoir ces problèmes encore, c'est pourquoi je disais que votre solution peut être ok dans votre environnement actuel. Mais il est toujours mieux d'être préparé à tout changement et à certaines de ces questions devraient être abordées.
PS: le C# ou Java, vous devez garder à l'esprit que ces langues ne sont pas à faire ce que vous dites. Ils sont en fait la compilation des fichiers de manière indépendante (comme les fichiers cpp) et les magasins de l'interface à l'échelle mondiale pour chaque fichier. Ces interfaces (et toutes les autres interfaces) sont ensuite utilisés pour lier l'ensemble du projet, c'est pourquoi ils sont capables de gérer les références circulaires. Parce que le C++ ne fait qu'une compilation de passer par fichier, il n'est pas en mesure à l'échelle mondiale des interfaces de magasin. C'est pourquoi vous devez les écrire explicitement dans les fichiers d'en-tête.
Vous ne comprenez pas comment le langage a été conçu pour être utilisé. .fichiers cpp sont vraiment (ou devrait l'être, à l'exception de inline et le modèle de code) les modules de code exécutable que vous avez dans votre système. .fichiers cpp sont compilées dans des fichiers objets sont ensuite liés ensemble. .h fichiers existent uniquement pour la déclaration anticipée de le code mis en œuvre .fichiers cpp.
Cela accélère le temps de compilation et plus petit exécutable. Aussi, il semble beaucoup plus propre parce que vous pouvez obtenir un aperçu rapide de votre classe, en regardant son .h déclaration.
Comme pour inline et le modèle de code - parce que ces deux sont utilisés pour générer du code par le compilateur et non pas l'éditeur de liens - ils doivent toujours être disponibles pour le compilateur par .fichier cpp. Donc la seule solution est de l'inclure dans votre .h fichier.
Cependant, j'ai développé une solution où j'ai ma déclaration de classe dans un .h de fichier, tous les modèles et le code en ligne dans une .inl fichier et la mise en œuvre de la non modèle/code en ligne dans mon .fichier cpp. L' .inl fichier est inclus dans le fond de mon .h fichier. Cela permet de maintenir les choses propre et cohérente.
L'inconvénient évident pour moi, c'est que vous avez toujours à construire tout le code à la fois. Avec
.cpp
fichiers, vous pouvez avoir compilation séparée, de sorte que vous êtes seulement à la reconstruction de bits qui ont vraiment changé.Vous pourriez vouloir vérifier Lazy C++. Il permet de placer le tout dans un seul fichier et puis il s'exécute avant de procéder à la compilation et divise le code dans .h et .fichiers cpp. Cela pourrait vous offrir le meilleur des deux mondes.
Ralentir les temps de compilation sont généralement dues à des excès de couplage à l'intérieur d'un système écrit en C++. Peut-être vous avez besoin de diviser le code en sous-systèmes avec des interfaces externes. Ces modules pourraient être rassemblés dans des projets séparés. De cette façon, vous pouvez réduire la dépendance entre les différents modules du système.
Une chose que vous êtes renoncer que j'aurais du mal à vivre sans est anonyme-espaces de noms.
Je trouve qu'ils sont incroyablement précieux pour la définition de la classe utilitaire fonctions qui doivent invisible en dehors de la classe de mise en œuvre de fichier. Ils sont aussi parfaits pour la tenue de toutes les données globales qui doivent être invisible pour le reste du système,comme une instance du singleton.
Vous allez à l'extérieur de la structure de la langue. Alors que vous pourriez avoir quelques avantages, il va finalement vous mordre dans le cul.
C++ est conçu pour les h des fichiers de déclarations et de fichiers cpp avoir des implémentations. Les compilateurs sont construits autour de cette conception.
Oui, les gens le débat de savoir si c'est une bonne architecture, mais c'est la conception. C'est mieux de passer votre temps sur votre problème que de réinventer de nouvelles façons de concevoir fichier C++ architecture.
Un inconvénient de cette démarche est que vous ne pouvez pas le faire en parallèle de la compilation. Vous pourriez penser que vous êtes l'obtention d'un compiler plus vite maintenant, mais si vous avez plusieurs .fichiers cpp, vous pouvez construire en parallèle sur plusieurs de base sur votre propre ordinateur ou à l'aide d'un distribuée du système de construction comme distcc ou Incredibuild.
J'aime à penser à propos de la séparation de l' .h et .fichiers cpp en termes d'interfaces et implémentations. L' .h les fichiers contiennent les descriptions de l'interface à un plus grand nombre de classes et le .rpc fichiers contiennent les implémentations. Il y a parfois des questions pratiques ou de clarté qui empêchent complètement propre séparation, mais c'est là que je commence. Par exemple, les fonctions d'accesseur en général, je code en ligne dans la déclaration de classe pour plus de clarté. Les plus grandes fonctions sont codées dans le .fichier cpp
Dans tous les cas, ne laissez pas le temps de compilation dicter la façon dont vous allez organiser votre programme. Mieux d'avoir un programme qui est lisible et maintenable sur celui qui compile en 1,5 minutes au lieu de 2 minutes.
Je crois qu'à moins d'utiliser MSVC est pré-compilé en-têtes, et vous êtes à l'aide d'un Makefile ou un autre basée sur les dépendances de construction du système, la séparation des fichiers source doivent compiler plus vite lors de la construction de manière itérative. Depuis, mon développement est presque toujours itératif, je m'inquiète plus sur la façon rapide, il peut recompiler les modifications que j'ai apportées dans le fichier x.cpp que dans les vingt autres fichiers source que je n'ai pas changé. En outre, je fais des changements beaucoup plus fréquemment à la source des fichiers que je ne l'Api pour qu'ils changent moins fréquemment.
Concernant, dépendances circulaires. Je prendrais paercebal les conseils d'un peu plus loin. Il avait deux classes qui ont des pointeurs vers les uns des autres. Au lieu de cela, j'ai couru dans le cas le plus souvent lorsqu'une classe nécessite une autre classe. Lorsque cela se produit, je inclure le fichier d'en-tête de la dépendance dans le fichier d'en-tête de l'autre classe. Un exemple:
.
.
La raison que je comprend, ce qui est légèrement sans rapport avec dépendances circulaires, est que je pense qu'il y a des alternatives, y compris les fichiers d'en-tête de bon gré mal gré. Dans cet exemple, la structure de la barre de fichier source ne comprend pas la struct foo fichier d'en-tête. Ceci est fait dans le fichier d'en-tête. Cela a un avantage, c'est que d'un développeur à l'aide de la barre n'a pas besoin de connaître tous les autres fichiers que le développeur aurait besoin d'inclure pour utiliser ce fichier d'en-tête.
Un problème avec le code d'en-têtes, c'est qu'il doit être insérée, sinon, vous aurez de multiples définition des problèmes lors de la liaison de plusieurs unités de traduction qui comprennent que même en-tête.
La question d'origine spécifié qu'il n'y a jamais qu'une seule rpc dans le projet, mais qui n'est pas le cas si vous êtes à la création d'un composant destiné à une bibliothèque réutilisable.
Par conséquent, dans l'intérêt de la création de la plus réutilisable et facile à gérer le code que possible, ne mettre inline et inlinable code dans les fichiers d'en-tête.
Bien, comme beaucoup l'ont souligné, il y a beaucoup d'inconvénients à cette idée, mais pour équilibrer un peu et de fournir un pro, je dirais que le fait d'avoir un code de bibliothèque entièrement dans les en-têtes de sens, puisqu'il sera indépendant des autres paramètres du projet, il est utilisé dans.
Par exemple, si l'on tente de rendre l'utilisation de différentes bibliothèques Open Source ils peuvent être mis à utiliser des approches différentes pour créer des liens vers votre programme - certains peuvent utiliser le système d'exploitation est chargée dynamiquement le code de bibliothèque, d'autres est lié statiquement; certains peuvent être configurés pour utiliser le multithreading, tandis que d'autres ne l'est pas. Et il peut très bien être une tâche écrasante pour un programmeur - surtout avec une contrainte de temps à essayer de classer ces approches incompatibles out.
Tout cela n'est cependant pas un problème lors de l'utilisation de bibliothèques qui sont contenues entièrement dans les en-têtes. "Ça marche" pour le bien-écrite de la bibliothèque.
statique-ou-global-variable kludges encore moins transparent, peut-être de l'onu-debuggable.
par exemple, le comptage du nombre total d'itérations pour l'analyse.
Dans MON kludged fichiers mettre ces éléments en haut de la rpc fichier les rend faciles à trouver.
Par "peut-être de l'onu-debuggable", je veux dire qui, d'habitude, je vais mettre une telle mondial dans la fenêtre espion. Puisqu'il s'agit toujours-dans-la portée de la fenêtre WATCH pouvez toujours obtenir n'importe où le compteur de programme, il arrive à être en ce moment. En mettant ces variables à l'extérieur d'un {} en haut d'un fichier d'en-tête vous laissez tout en aval de code de "voir" à eux. En les plaçant à l'INTÉRIEUR d'un {}, désinvolte, je pense que le débogueur n'aura plus de considération pour eux de la "portée" si votre programme-compteur est à l'extérieur de la {}. Alors qu'avec la bidouille-global-à-Rpc-dessus, même s'il peut être global de la mesure de voir dans votre lien-carte-apb-etc, sans extern-déclaration de l'autre fichiers Cpp ne peuvent pas l'obtenir, en évitant accidentelle de couplage.
Une chose que personne n'a abordé est celui de la compilation des fichiers volumineux nécessite un beaucoup de la mémoire. La compilation de l'intégralité de votre projet à la fois exiger un tel énorme espace de mémoire qu'il n'est tout simplement pas réalisable, même si vous pourriez mettre tout le code dans les en-têtes.
Si vous utilisez des classes template, vous devez mettre l'ensemble de la mise en œuvre dans l'en-tête de toute façon...
De la compilation de l'ensemble du projet en une seule fois (par le biais d'une seule base .fichier cpp) devrait permettre à quelque chose comme "Tout le Programme d'Optimisation" ou "Cross-Module d'Optimisation", qui n'est disponible que dans quelques avancées compilateurs. Ce n'est pas vraiment possible avec un compilateur standard si vous êtes à la précompilation de tous vos .rpc fichiers dans les fichiers objets, puis de la liaison.
Le important de la philosophie de la Programmation Orientée Objet réside dans le fait d'avoir des données cacher conduisant à encapsulé classes avec la mise en œuvre est cachée par les utilisateurs. C'est principalement pour fournir une couche d'abstraction où les utilisateurs d'une classe utilisent essentiellement la mise à disposition du public des fonctions membres d'instance spécifique ainsi que les types statiques. Ensuite, le développeur de la classe est libre de modifier les implémentations réelles fourni les implémentations ne sont pas exposées aux utilisateurs. Même si la mise en œuvre est privée et a déclaré dans un fichier d'en-tête, la modification de la mise en œuvre exigera de tous dépendants de la base de code à la compilation. Considérant que, si la mise en œuvre (définition des fonctions membres) sont dans un code source (non-fichier d'en-tête), puis la bibliothèque est changé, et la variable dépendante de la base de code doit rétablir le lien avec la version révisée de la bibliothèque. Si cette bibliothèque est liée de façon dynamique un, comme une bibliothèque partagée puis en gardant la signature de la fonction (interface) même et la mise en œuvre changé ne nécessite pas de re-lier aussi. Avantage? Bien sûr.