10
votes

C ++ Politique d'en-tête sur les grands projets (Redux)

J'ai lu tout ce que je pouvais trouver sur ce sujet, y compris quelques discussions très utiles sur ce site, les directives de codage de la NASA et les directives Google C ++. J'ai même acheté le livre "Physique C ++ Design" recommandé ici (désolé, oublié le nom) et avoir des idées utiles à partir de cela. La plupart des sources semblent être d'accord - les fichiers d'en-tête doivent être autonomes, c'est-à-dire qu'ils incluent ce dont ils ont besoin pour qu'un fichier CPP puisse inclure l'en-tête sans y compris les autres et cela compilerait. J'ai également le point de déclarer avant de déclarer plutôt que d'inclure chaque fois que possible.

qui dit, comment direct si foo.cpp inclut bar.h et qux.h , mais il s'avère que bar.h elle-même inclut qux.h ? Devrait foo.cpp , puis évitez y compris qux.h ? Pro: nettoie foo.cpp (moins "bruit"). Con: si une personne change de bar.h ne peut plus inclure qux.h , foo.cpp mystérieusement commence à ne pas compiler. Cause également la dépendance entre foo.cpp et qux.h ne pas être évident.

Si votre réponse est "Un fichier de RPC devrait #include chaque en-tête dont il a besoin", pris dans sa conclusion logique qui signifierait que tous les fichiers du CPP comptent #include etc. puisque la plupart des codes finiront par l'utiliser et si vous n'êtes pas censé compter sur une autre en-tête, y compris, votre RPC doit les inclure explicitement. Cela ressemble à beaucoup de "bruit" dans les fichiers du CPP.

pensées?

Discussions précédentes:

Quelles sont quelques techniques pour la compilation de limitation Dépendances dans les projets C ++?

Votre politique préférée C / C ++ Inditeur pour les grands projets?

Comment automatiser la recherche de directives non utilisées inutilisées?

ETA: Inspiré des discussions précédentes ici, j'ai écrit un script Perl pour commenter successivement chaque "Inclure" et "utiliser", puis tenter de recompiler le fichier source, de déterminer ce qui n'est pas nécessaire. J'ai également compris comment l'intégrer avec VS 2005, vous pouvez donc double-cliquer pour aller sur "Non utilisé". Si quelqu'un le veut, laissez-moi savoir ... très expérimental en ce moment cependant.


8 commentaires

Je suppose que cela devrait être un wiki communautaire puisqu'il est éventuellement subjectif.


Si vous vous souvenez de ce que le vrai nom du livre «physique C ++» est, veuillez éditer la question - je voudrais vérifier (si je ne l'ai pas déjà).


Le livre peut être "de la conception du logiciel C ++ à grande échelle" de John Lakos: Amazon.com/...


@Michael: Si c'est lakos (que je soupçonne aussi), alors c'est une très bonne lecture, mais comme c'est très vieux, prenez-la avec un grain de sel. (Ou même un pot de sel, peut-être. C'est très il y a longtemps que je le lis.)


Oui, c'est celui-ci; C'est à peu près le seul jeu en ville pour la conception physique C ++. Je l'ai trouvé très instructif et pas du tout daté. Les principes s'appliquent toujours au développement C ++.


+1 pour Lakos; Excellent livre.


Les conseils de l'IIRC, lakos sur Inclure les gardes des fichiers d'en-tête sont obsolètes. Il préconisait les mettre en dehors de la #include, pour éviter la pénalité de la performance de la lecture de fichiers inutiles. Des compilateurs plus modernes permettent des pragmes ou de la reconnaissance et des gardes spéciaux comprennent des gardes.


"Je l'ai trouvé très instructif et pas du tout daté." Alors écrivez-vous vos propres itérateurs alors? (chapitre 1).


7 Réponses :


8
votes

Si votre réponse est "Un fichier CPP doit #include chaque en-tête dont il a besoin", pris dans sa conclusion logique qui signifierait que chaque fichier de cpp a à #include , etc. puisque la plupart des codes finiront par l'utiliser, et si vous n'êtes pas censé compter sur une autre en-tête, y compris, votre RPC doit les inclure explicitement.

yup. C'est comme ça que je le préfère.

Si le «bruit» est trop important, il est correct d'avoir un fichier «global» incluant l'ensemble habituel d'inclut (comme stdafx.h dans beaucoup de fenêtres programmes), et incluent qu'au début de chaque fichier .cpp (qui aide également aux en-têtes précompilés).


2 commentaires

En fait, la chose STDAFX est un modèle que j'essaie de vous échapper. Un problème est que notre code doit être multiplate-forme, et autant que je puisse voir, PCH ne fonctionne pas de la même manière dans VC ++ et GCC. En outre, nous avons fini par une "N carré", notamment une complexité puisque tout a fini par tous, y compris tout, conduisant à de longues périodes de compilation. Je suppose que nous pourrions mettre la lettre stl inclut dans un lieu commun, mais cela signifie que chaque unité de compilation les obtient s'il souhaite ou non. J'ai également remarqué que les en-têtes STL s'incluent, de sorte que si vous incluez (par exemple, «chaîne», vous n'avez peut-être pas besoin de «mémoire» (juste un exemple).


Je peux comprendre pourquoi vous ne voudriez pas utiliser le modèle STDAFX.H, mais cela peut fonctionner et c'est un modèle que je ne considère pas la «mauvaise forme». En outre, je ne préconisais pas tout jeter et l'évier de la cuisine dans l'en-tête STDAFX - juste les en-têtes qui vraiment sont très utilisés partout. Le fichier .CPP peut toujours inclure les autres en-têtes moins fréquemment nécessaires après le fichier STDAF.H. Et je ne pense pas que je m'inquiéterais de multiples en-têtes indispensables, ils ne devraient pas trop affecter les temps de construction (l'inclusion ultérieure d'un fichier ne sera pas traitée).



2
votes

Je pense que vous devriez toujours inclure les deux fichiers. Cela aide à maintenir le code.

// Foo.cpp

#include <Library2>


2 commentaires

Souhaitez-vous aussi étendre cela aux bibliothèques stl aussi? par exemple. Si un fichier CPP utilise "String", vous voudrez voir l'inclusion? Dans chaque fichier qui utilise "string"? Je peux certainement voir votre raisonnement pour les en-têtes de l'utilisateur, mais en consultant mon code, la plupart des fichiers sources devront avoir un tas de STL comprennent en haut.


Attends, qu'est-ce que tu ferais d'autre avec eux? Si votre classe utilise des chaînes dans l'en-tête, cela irait là, mais s'il s'agissait simplement de la mise en œuvre qui le poursuivait, cela irait dans le CPP. Chaque cpp doit inclure l'en-tête dont il a besoin.



0
votes

Chaque fichier d'en-tête fournit des définitions nécessaires pour utiliser un service de type (une définition de classe, certaines macros, peu importe). Si vous utilisez directement les services exposés via un fichier d'en-tête, incluez ce fichier d'en-tête même si un autre fichier d'en-tête comprend également. D'autre part, si vous utilisez ces services uniquement indirectement, uniquement dans le contexte des autres services du fichier d'en-tête, n'incluez pas directement le fichier d'en-tête.

À mon avis, ce n'est pas une grosse affaire de toute façon. La deuxième inclusion d'un fichier d'en-tête ne s'approche pas complètement; Il est essentiellement commenté pour tous sauf la première fois qu'il est inclus. Comme les choses changent et que vous découvrez que vous utilisiez un fichier d'en-tête que vous n'étiez pas directement, ce n'est pas un gros problème de l'ajouter.


0 commentaires

2
votes

Je pense que vous devriez inclure les deux jusqu'à ce qu'il devienne insupportable. Refactorez ensuite vos classes et fichiers source pour réduire le couplage, sur des motifs que, si vous ne faites que répertorier toutes vos dépendances est onéreuse, vous avez probablement trop de dépendances ...

Un compromis peut être de dire que si quelque chose dans bar.h contient une définition de classe (ou une déclaration de fonction) qui nécessite nécessairement une autre définition de classe à partir de qux.h , alors bar.h peut être supposé / requis pour inclure qux.h et l'utilisateur de l'API dans bar.h ne doit pas avoir la peine y compris les deux.

Alors, supposons que le client veuille penser à lui-même comme "vraiment" que "vraiment" ne se intéresse que dans la barre API, mais doit également utiliser qux, car il appelle une fonction de barre qui prend ou renvoie un objet qux par valeur. Ensuite, il peut simplement oublier que qux a sa propre tête et imaginer qu'il s'agit d'une grande API de barre définie dans bar.h , avec qux faisant partie de cette API.

Cela signifie que vous ne pouvez pas compter sur, par exemple, rechercher des fichiers CPP pour les mentions de qux.h pour trouver tous les clients de qux. Mais je ne voudrais jamais compter sur cela de toute façon, puisqu'il est en général, il est trop facile de manquer accidentellement d'une dépendance déjà incluse indirectement et de ne jamais réaliser que vous n'avez pas répertorié tous les en-têtes pertinents. Donc, vous ne devriez probablement pas en aucun cas supposer que les listes d'en-tête sont complètes, mais utilisez plutôt Doxygen (ou GCC -M, ou autre) pour obtenir une liste complète des dépendances et rechercher cela.


1 commentaires

Merci ... C'est un bon point. Rampant comprend pourriez-vous être une odeur de code, un signe d'accouplement excessif. Je vais certainement jeter un coup d'œil à cela.



1
votes

Que diriez-vous si foo.cpp inclut la bar.h et qux.h, mais il s'avère que la barre.h elle-même comprend qux.h? FEOO.CPP devrait-il alors éviter d'inclure Qux.h?

Si foo.cpp utilise quelque chose à partir de qux.h directement, il devrait inclure cet en-tête lui-même. Sinon, étant donné que bar.h nécessite qux.h , je vous fierais sur bar.h y compris tout ce dont il a besoin.


0 commentaires

0
votes

Une façon de penser si foo.cpp utilise directement qux.h, est de penser à ce qui se passerait si foo.cpp n'a plus besoin d'inclure la bar.H. Si foo.cpp aurait toujours besoin d'inclure qux.h, il devrait alors être énuméré explicitement. Si cela n'aurait plus besoin de rien de qux.h, il n'est probablement pas nécessaire de l'inclure explicitement.


0 commentaires

0
votes

La règle "Ne jamais optimiser sans profilage" s'applique ici, je pense.

(cette réponse est biaisée vers "performance" sur "encombrement"; le fouillis peut généralement être nettoyé à volonté, il devient certes plus difficile la plus tard, mais la performance vous impose régulièrement.)

Essayez-le des deux manières, voir s'il y a une amélioration de la vitesse significative de votre compilateur, puis envisagez de supprimer des en-têtes "en double" - car d'autres réponses soulignent, il y a une pénalité de maintenabilité à long terme que vous prenez en supprimant les doublons .

envisager de recevoir quelque chose comme Sysinternals filemon pour voir si vous " Réfléchir réellement aux touches de fichier de fichiers pour ceux-ci incluent (la plupart des compilateurs ne seront pas, avec des gardes d'en-tête corrects).

Notre expérience ici indique que de rechercher activement des en-têtes complètement inutilisés (ou pourraient être, avec des déclarations appropriées) et les supprimer est beaucoup plus digne de temps et d'efforts que de déterminer les chaînes incluantes. pour des doublons potentiels. Et un bon peluche (attelle, PC-Lint , etc.) peut vous aider avec cette détermination.

Encore plus digne de notre temps et de notre effort visant à obtenir notre compilateur pour gérer plusieurs unités de compilation par exécution (presque une vitesse linéaire pour nous, jusqu'à un point - la compilation a été complètement dominée par le démarrage du compilateur).

Après cela, vous voudrez peut-être envisager la folie "simple CPP". Cela peut être assez impressionnant.


0 commentaires