Bonjour,
supposons que dans un fichier d'en tête j'ai besoin de foo.h et de bar.h
Mais que bar.h inclut déjà foo.h (donc je peux me contenter d'inclure seulement bar.h).
Est-ce que j'inclus les deux ou seulement bar.h ?
Merci.
Bonjour,
supposons que dans un fichier d'en tête j'ai besoin de foo.h et de bar.h
Mais que bar.h inclut déjà foo.h (donc je peux me contenter d'inclure seulement bar.h).
Est-ce que j'inclus les deux ou seulement bar.h ?
Merci.
Pour moi, la règle est assez simple :
- Si tu as besoin d'un composant de bar.h, tu inclus bar.h
- Si tu as besoin d'un composant de foo.h, tu inclus foo.h
Si bar.h a besoin d'un composant de foo.h il doit l'inclure. Un include file devrait être complet et ne devrait forcer l'utilisateur à rechercher ce dont à besoin bar.h pour l'inclure avant.
De toute façon, si foo.h est inclus 2 fois (par ton module et par bar.h inclus par ton module), cela ne doit pas poser de problème non plus.
+1 ... J'inclus les fichiers qu'il faut quand il faut. De toute manière, si tu inclus le même fichier dans différents headers ou sources, si c'est bien fait (protection contre les inclusions multiples) y'a aucun problème !
Il y en a pourtant un, de problème : ralentissement pouvant être très sensible de la vitesse de compilation, et pollution assez lourde d'un graphe de dépendance si l'on décide (ou que l'on a l'habitude) d'en utiliser... Ce qui pose d'énormes problèmes d'ailleurs lorsque l'on veut effectuer la séparation d'un module en deux parties, ou simplement analyser les dépendances.
Pour ma part, c'est plutôt le contraire : je vise à vérifier / m'assurer que l'on n'inclus pas les entêtes de façon inutile et/ou redondante.
Par contre, je vise également à ce que les entêtes .H soient complets (= ne requièrent pas d'inclusions additionnelles dans un C/CPP qui les utiliseraient) : il est donc hors de question de supprimer une inclusion "comme ça", il faut malgré tout que la chaîne d'inclusion totale soit correcte ET complète.
Bonjour,
- Sémantiquement, tu dois inclure le fichier qui définit l'entité que tu utilises ;
- Techniquement, rien ne garantit la manière dont le fichier inclus sera écrit. D'une plate-forme à l'autre, il se peut très bien que le sous-fichier inclus ne le soit plus. Il faut donc agir comme si tu ne pouvais voir l'intérieur de ce fichier.
Plus précisément, il n'y a pas de relation hiérarchique entre les *.h comme il y en a dans un arbre de classes objet, par exemple. bar.h « n'implique pas » foo.h. Par ailleurs, la directive #include sert à inclure n'importe quoi, pas forcément des *.h. C'est assez rare de voir autre chose, bien sûr, mais c'est le cas avec les *.xpm, par exemple.
Pour éviter que le contenu d'un *.h soit redéfini plusieurs fois, on l'encadre généralement avec des macros du genre :
Code C : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 #ifndef FOO_H #define FOO_H 1 ... [contenu] ... #endif
Il te faudra penser à le faire à ton tour si, toi aussi, tu écris une bibliothèque et fournis son *.h.
Ce n'est pas sa question : il ne s'agit pas de coller des headers classique pour l'hygiène mais, au contraire, de savoir s'il faut volontairement omettre un header requis parce qu'un autre est réputé l'avoir inclus lui-même auparavant. La réponse est « non », pour les raisons exposées ci-dessus.
Pour les délais de compilation, aucun overhead notable n'est introduit si le fichier est encadré par les bonnes macros. Son interprétation prend fin dès le départ, lorsque la ligne idoine est atteinte. Pour les graphes des dépendances, celles-ci peuvent former un cycle sans que ce soit forcément une erreur. C'est donc à toi (ou à ton logiciel traceur) de conserver une liste de chaque fichier inclus et d'interrompre la progression de la recherche lorsqu'il est fait référence à un fichier de cette liste.
Chose pourtant classiquement faite sur pas mal d'entêtes standards et/ou d'OS... Typiquement, si tu développes sous Windows, je pense que tu auras rarement inclus "winbase.h" et que tu inclus directement "windows.h"...
Pour le "réputé", il y a aussi le fait d'avoir des entêtes complets sans scories (= TOUS les entêtes requis par l'utilisation du .H, et SEULEMENT ces entêtes). Donc, il n'y a pas de "réputé" : si tu utilises un module XX qui manipule des std::string, il est inutile d'inclure toi-même <string> : le header "XX.h" le fait pour toi, s'il est complet. Et tu peux généraliser ça à toute sous-classe, entité ou type manipulé par la classe que tu as utilisée.
Si par contre tu "profites" d'une inclusion de <string> via un header qui ne manipule pas publiquement de chaînes, là, c'est effectivement mauvais de conserver cette inclusion : il faudrait la supprimer du header qui ne l'utilise pas.
L'absence de macros peut (et souvent, va) provoquer des warnings au mieux, des erreurs au pire. Pour le reste, le fichier est malgré tout inclus et passé au préproc, même s'il ne génère que des lignes vides. Si c'est juste un .H à toi de 20 lignes, ce n'est pas trop grave. Si c'est "windows.h" et toute sa tripaille que tu inclus N fois pour rien, ça commence à devenir nettement plus sérieux, et plus tu multiplies ce phénomène, plus l'impact est lourd. Pour ma part, je vois nettement le souci sur les inclusions des entêtes ACE, par exemple : en rajouter "pour rien", c'est un ralentissement sensible de la vitesse de compilation... Surtout que si tu exagères un peu trop, tu finis par avoir besoin de swapper. OK, les fichiers peuvent être dans le cache, mais sur une très (trop ?) grosse partie, tu vas te retrouver avec deux processus qui réclament la RAM : le compilateur pour le fichier en cours de compilation, et le cache pour les maintenir disponibles.
Fais le test avec cet entête :Si tu l'inclus plusieurs fois, tu le verras plusieurs fois définies dans le fichier prétraité (-E), et les numéros de ligne vérifiés (et affichés) à chaque inclusion de cet entête. Suivant le compilateur et les réglages, tu auras ou non les lignes vides maintenues dans le fichier prétraité.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8 const int TEST_INCLUDE_START = 0 ; #ifndef TEST_INCLUDE #define TEST_INCLUDE // Rajouter N lignes vides et/ou copier ce commentaire #endif const int TEST_INCLUDE_END = 1 ;
En l'occurrence, Doxygen n'effectue pas cette coupe (et heureusement, d'ailleurs, ça permet aussi de résoudre des problèmes de complétude des entêtes). Quant aux dépendances cycliques (ou encore pire : inutiles), c'est de toutes façons quelque chose d'assez crasseux pour laquelle il est quand même souhaitable de se poser la question "ai-je correctement fait ma conception de modules ?"...
euh... tu cites C++ et Windows...
Ayant toujours développé en C pour unixoides, je ne connais pas les pratiques ni C++, ni Win, mais je peux t'assurer que ce n'est pas la pratique recommandée (ni utilisée, voir les sourceforge ou autres archives "avant C++") en C sous unixoides...
En général, par exemple sur un projet, tu fais un "CommonC.h", qui va inclure les headers généraux dont on a toujours besoin (stdio, stdlib, time, limits, ...), et tu t'en sers..
Tu as souvent suffisamment de headers à gérer avec les différents modules pour ne pas en plus te traîner à recopier à chaque fois tout ça...
Enfin, c'est mon expérience....
Disons que la problématique des entêtes est un point commun avec les deux langages (pour une fois...). Tu peux remplacer mon std::string par un time_t si tu préfères et obtenir le même cas d'utilisation.
Après, Windows est aussi un cas fréquent pour le dév, ça reste une part majoritaire des PC existants. C'est moins sensible sur les entêtes du kernel Linux, mais ça reste vrai sur les entêtes de librairies un peu plus complexes et/ou graphiques qui trainent en général des "monstres" derrière le premier include, ou encore la plupart des frameworks portables tels que ACE.
Et tu rends ainsi tous tes modules dépendants d'un autre, ou trainant des dépendances inutiles pour des modules qui pourraient ne dépendre QUE d'entêtes standards.
Tu le sais très certainement aussi bien que moi, cet entête "commun" va vite hériter de définitions propres au projet, ou d'un lien vers un module quelconque, ou d'une dépendance à une librairie. C'est comme ça, en général, que l'on finit par avoir une IHM graphique qui te réclame un entête de kernel... Une aberration, à mon sens.
Cerise sur le gâteau, dans la plupart des cas, cet entête commun n'est même pas précompilé... Ou alors, au contraire, on a l'effet inverse (courant hélas sous Windows via stdafx.h, l'entête précompilé créé automatiquement) : TOUTES les dépendances sont incluses dedans, et chaque fichier C/CPP n'inclus QUE l'entête précompilé. Le jour où tu dois démêler les spaghettis, ou lorsque tu rajoutes un #include, tu pleures...
Si effectivement tu le fais en copier/coller, on est d'accord : c'est mal. Notamment parce que tu as la plupart du temps une dépendance inutile qui est rajoutée.
Pour ma part, je ne recopie pas : comme je l'ai dit, je fais des entêtes complets. J'ajoute au cas par cas les #include dans le .C / .CPP en fonction de mes besoins, et si ça demande l'inclusion d'une nouvelle librairie, je me pose sérieusement la question "en ai-je réellement besoin ?", ce qui me permet de soit me rendre compte que je vais "trop loin" dans ma fonctionnalité, soit que j'ai oublié une séparation de code qui devrait être présente.
Mon but premier, c'est d'avoir les modules les plus indépendants possibles, connectés entre eux par des interfaces "abstraites" et génériques. La raison est de pouvoir tester unitairement les modules au maximum via un stub, et d'obtenir une portabilité maximale via les interfaces.
Certes, c'est une problématique propre à mon métier et à mon expérience. Mais pour avoir souvent dû remettre en ordre du code fait par des windowsiens / linuxiens "spécialistes", je peux te garantir que tu apprends assez vite à avoir une démarche un peu différente de la démarche "habituelle". Et je ne te parles même pas du nombre de fois où j'ai pu déplacer des modules d'une librairie à l'autre pour de simples raisons de dépendances crétines (ou pire : circulaires !!) faites par des inclusions sauvages et/ou via un "common.h"... Quand tu n'arrives pas à la situation délirante suivante :
- Ça ne compile pas !
- Normal, tu n'as pas inclus "bouse.h".
- Mais si, je l'ai inclus pourtant.
- Ah oui, mais tu l'as inclus après "immonde.h", il faut l'inclure avant.
- Mais pourquoi ?
- On ne sait pas, celui qui l'a fait est parti il y a deux ans, et personne n'ose y toucher.
Bien entendu, c'est du vécu, tu t'en doutes. En l'occurrence, on trainait une librairie infecte importée en partie par l'un des headers, en partie par l'autre, et à un moment donné il y avait une redéfinition non protégée (et le warning était planqué via un #pragma)...
Il est toujours bon de savoir que ce que l'on considère comme "bon", "souhaitable" ou "pratique" peut être une vraie plaie ouverte dans d'autres situations, et surtout pourquoi.
Je pense, oui, car :
je n'ai jamais vu ceci... JAMAIS....
Et c'est même le contraire.....
Les entêtes se font par foncionalités, par modules...
Il est certain que si on se met à faire "classes" en tête, on va droit dans le mur..
Mais la plupart des codes sur lesquels j'ai travaillé étaient par décuopuages fonctionnels, et donc en général aucun problème..
Un "CommonC" ne sera jamais touché une fois le démarrage du projet effectué.
Une IHM aura un "MainWin.h", un "PrintWin.h", etc
Il y aura un "MATHS.h", un "Images.h", etc etc....
Chacun de ces derniers incluera CommonC, c'est sûr puqique le projet est en C.
Mais pour le reste, tout sera inclus en fonction du besoin uniquement...
Franchement, je crois (alors je ne sais pas si c'est le monde Win ou la proximité de l'enseignement OO qui veut ça) que c'est ton milieu/expérience qui crée ce genre de choses....
Ben tu as bien de la chance, ou alors, tu avais peu de personnes bossant sur le projet... Avec une valse fréquente d'assistants et/ou de pseudo-dévs qui tripotent suivant les trous de planning, cela arrive constamment pourtant... Et j'ai constaté ça aussi dans d'autres domaines que le mien (lorsqu'il y a valse d'intervenants), c'est même le sujet préféré d'anecdotes avec mes amis qui bossent également dans l'informatique (le fameux "Tu sais pas la dernière qu'ils m'ont pondu, ces enclumes ?" "Racontes !").
Chez nous aussi. C'est justement là le problème, aussi : va trouver un point commun entre un système d'acquisition temps réel sur des cartes électroniques, et un routeur TCP/IP aux communications bas niveau et à l'envoi de données vers les IHM... Les deux sont des threads du même processus, bien entendu. Rajoutes à ça le fait d'être multi plate-forme (tout en restant optimisé et temps réel, bien entendu), et tu vas commencer à mieux cerner le problème.
Au final, t'as TOUJOURS quelqu'un qui ajoute un truc quelque part qui "pollue" les autres composants, si l'on part sur le principe de faire des entêtes de facilité.
Tout à fait d'accord : on inclut suivant le BESOIN. Je ne vois pas l'intérêt d'inclure "math.h" si l'on ne fait pas d'opérations mathématiques dans un module, pour ma part... Simplement, moi, je le fais dès le début.
Ou plus simplement de travailler sur des applications qui fonctionnent, sont améliorées et maintenues pendant 10 à 25 ans... Avec ce que cela implique comme turn-over et évolutions technologiques pendant cette période.
Après, comme je l'ai déjà dit plusieurs fois, le milieu industriel / embarqué est un milieu caché : cela ne veut pas dire qu'il est inexistant ou négligeable en terme de postes pourvus, bien au contraire, et le problème que j'expose est malheureusement quasi-inévitable sur un projet devant être maintenu aussi longtemps.
Vous avez un bloqueur de publicités installé.
Le Club Developpez.com n'affiche que des publicités IT, discrètes et non intrusives.
Afin que nous puissions continuer à vous fournir gratuitement du contenu de qualité, merci de nous soutenir en désactivant votre bloqueur de publicités sur Developpez.com.
Partager