Les fichiers d'en-tête sont-ils vraiment bons? [fermé]

20

Je trouve que les fichiers d'en-tête sont utiles lors de la navigation dans les fichiers source C ++, car ils donnent un "résumé" de toutes les fonctions et données membres d'une classe. Pourquoi tant d'autres langages (comme Ruby, Python, Java, etc.) n'ont-ils pas une fonctionnalité comme celle-ci? Est-ce un domaine où la verbosité de C ++ est utile?

Matt Fichman
la source
Je ne connais pas les outils en C et C ++, mais la plupart des IDE / éditeurs de code dans lesquels j'ai travaillé ont une vue "structure" ou "contour".
Michael K
1
Les fichiers d'en-tête ne sont pas bons - ils sont nécessaires! - Voir aussi cette réponse sur SO: stackoverflow.com/a/1319570/158483
Pete

Réponses:

31

Le but initial des fichiers d'en-tête était de permettre la compilation en une seule passe et la modularité en C. En déclarant les méthodes avant leur utilisation, il ne permettait qu'une seule passe de compilation. Cet âge est révolu depuis longtemps grâce à nos ordinateurs puissants capables de faire de la compilation multi-passes sans aucun problème, et parfois même plus rapidement que les compilateurs C ++.

C ++ étant rétrocompatible avec C, il fallait conserver les fichiers d'en-tête, mais en ajoutant beaucoup au-dessus d'eux, ce qui a entraîné une conception assez problématique. Plus dans FQA .

Pour la modularité, les fichiers d'en-tête étaient nécessaires en tant que métadonnées sur le code dans les modules. Par exemple. quelles méthodes (et dans les classes C ++) sont disponibles dans quelle bibliothèque. Il était évident que le développeur écrivait ceci, car le temps de compilation était coûteux. De nos jours, ce n'est pas un problème que le compilateur génère ces métadonnées à partir du code lui-même. Les langages Java et .NET le font normalement.

Donc non. Les fichiers d'en-tête ne sont pas bons. Ils étaient quand nous devions encore avoir compilateur et éditeur de liens sur des disquettes séparées et la compilation prenait 30 minutes. De nos jours, ils ne font que gêner et sont signe de mauvaise conception.

Euphorique
la source
8
Je suis d'accord avec la plupart de ce que vous écrivez, mais le dernier paragraphe simplifie un peu les choses. Les fichiers d'en-tête servent toujours à définir des interfaces publiques.
Benjamin Bannier
3
@honk C'est à cela que je voulais en venir avec ma question. Mais je pense que les IDE modernes (comme suggéré par ElYusubov) nient en quelque sorte cela.
Matt Fichman
5
Vous pourriez ajouter que le comité C ++ travaille sur des modules qui permettraient à C et C ++ de fonctionner sans en-têtes OU avec des en-têtes utilisés de manière plus efficace. Cela rendrait cette réponse plus juste.
Klaim
2
@MattFichman: Générer un fichier d'en-tête est une chose (que la plupart des éditeurs / IDE du programmeur peuvent faire automatiquement d'une certaine manière), mais le point sur une API publique est qu'elle doit être définie et conçue par un véritable humain.
Benjamin Bannier
2
@honk Oui. Mais il n'y a aucune raison pour que cela soit défini dans des fichiers séparés. Il peut être dans le même code que l'implémentation. Tout comme C # le fait.
Euphoric
17

Bien qu'ils puissent vous être utiles comme forme de documentation, le système entourant les fichiers d'en-tête est extrêmement inefficace.

C a été conçu pour que chaque étape de compilation crée un seul module; chaque fichier source est compilé dans une exécution distincte du compilateur. Les fichiers d'en-tête, en revanche, sont injectés dans cette étape de compilation pour chacun des fichiers source qui les référencent.

Cela signifie que si votre fichier d'en-tête est inclus dans 300 fichiers source, il est analysé et compilé encore et encore, 300 fois pendant la construction de votre programme. La même chose avec le même résultat, encore et encore. C'est une énorme perte de temps, et c'est l'une des principales raisons pour lesquelles les programmes C et C ++ sont si longs à construire.

Toutes les langues modernes évitent intentionnellement cette absurde petite inefficacité. Au lieu de cela, généralement dans les langues compilées, les métadonnées nécessaires sont stockées dans la sortie de génération, permettant au fichier compilé d'agir comme une sorte de référence de recherche rapide décrivant ce que contient le fichier compilé. Tous les avantages d'un fichier d'en-tête, créé automatiquement sans travail supplémentaire de votre part.

Alternativement dans les langages interprétés, chaque module qui est chargé reste en mémoire. Référencer ou inclure ou nécessiter une bibliothèque lira et compilera le code source associé, qui restera résident jusqu'à la fin du programme. Si vous en avez également besoin ailleurs, aucun travail supplémentaire car il a déjà été chargé.

Dans les deux cas, vous pouvez "parcourir" les données créées par cette étape à l'aide des outils de la langue. Typiquement, l'EDI aura un navigateur de classe quelconque. Et si le langage a un REPL, il peut aussi souvent être utilisé pour générer un résumé de la documentation de tous les objets chargés.

tylerl
la source
5
pas strictement vrai - la plupart, sinon la totalité, des compilateurs précompilent les en-têtes tels qu'ils les voient, donc les inclure dans la prochaine unité de compilation n'est pas pire que de trouver le bloc de code précompilé. Ce n'est pas différent, disons, du code .NET qui «construit» à plusieurs reprises system.data.types pour chaque fichier C # que vous compilez. La raison pour laquelle les programmes C / C ++ prennent autant de temps est qu'ils sont réellement compilés (et optimisés). Tout ce dont un programme .NET a besoin est d'être analysé en IL, le runtime fait la compilation réelle via JIT. Cela dit, 300 fichiers source en code C # prennent également un certain temps à compiler.
gbjbaanb
7

Ce que vous entendez par parcourir le fichier pour les fonctions et les membres de données n'est pas clair. Cependant, la plupart des IDE fournissent des outils pour parcourir la classe et voir les membres des données de classe.

Par exemple: Visual Studio a Class Viewet Object browserfournit joliment les fonctionnalités demandées. Comme dans les captures d'écran suivantes.

entrez la description de l'image ici

entrez la description de l'image ici

Yusubov
la source
4

Un inconvénient supplémentaire des fichiers d'en-tête est que le programme dépend de l'ordre de leur inclusion, et le programmeur doit prendre des mesures supplémentaires pour garantir l'exactitude.

Cela, couplé au système macro de C (hérité de C ++), conduit à de nombreux pièges et situations confuses.

Pour illustrer, si un en-tête définissait un symbole à l'aide de macros pour son utilisation et qu'un autre en-tête utilisait le symbole d'une autre manière, comme le nom d'une fonction, alors l'ordre d'inclusion influencerait grandement le résultat final. Il existe de nombreux exemples.

jcora
la source
2

J'ai toujours aimé les fichiers d'en-tête car ils fournissent une forme d'interface à l'implémentation, ainsi que des informations supplémentaires telles que les variables de classe, le tout dans un fichier facile à visualiser.

Je vois beaucoup de code C # (qui n'a pas besoin de 2 fichiers par classe) écrit en utilisant 2 fichiers par classe - un l'implémentation de classe réelle et un autre avec une interface. Cette conception est bonne pour la simulation (essentielle dans certains systèmes) et aide à définir la documentation de la classe sans avoir à utiliser l'EDI pour afficher les métadonnées compilées. J'irais si loin pour dire sa bonne pratique.

Donc, C / C ++ rendant obligatoire une équivalence (en quelque sorte) d'une interface dans les fichiers d'en-tête est une bonne chose.

Je sais qu'il y a des partisans d'autres systèmes qui ne les aiment pas pour des raisons comme `` il est plus difficile de pirater le code si vous devez mettre des choses dans 2 fichiers '', mais mon attitude est que le piratage du code n'est pas une bonne pratique de toute façon , et une fois que vous aurez commencé à écrire / concevoir du code avec un peu plus de réflexion, vous définirez naturellement les en-têtes / interfaces.

gbjbaanb
la source
Interface et implémentation dans différents fichiers? Si vous en avez besoin par conception, il est encore très courant de définir l'interface (ou la classe abstraite) dans des langages tels que Java / C # dans un fichier et de masquer la ou les implémentations dans d'autres fichiers. Cela ne nécessite pas de fichiers d'en-tête anciens et difficiles.
Petter Nordlander
hein? n'est-ce pas ce que j'ai dit - vous définissez l'interface et l'implémentation de la classe dans 2 fichiers.
gbjbaanb
2
eh bien, cela ne nécessite pas de fichiers d'en-tête et cela n'améliore pas les fichiers d'en-tête. Oui, ils sont nécessaires dans les langues héritées, mais en tant que concept, ils ne sont pas nécessaires pour diviser l'interface / implémentation dans différents fichiers (en fait, ils aggravent les choses).
Petter Nordlander
@Petter Je pense que vous vous méprenez, conceptuellement, ils ne sont pas différents - un en-tête existe pour fournir une interface dans le langage C, et bien que vous ayez raison - vous pouvez combiner les 2 (comme beaucoup le font avec du code C uniquement en-tête) ce n'est pas un les meilleures pratiques que j'ai vues suivies à de nombreux endroits. Tous les développeurs C # que j'ai vus séparent l'interface de la classe, par exemple. C'est une bonne pratique de le faire car vous pouvez alors inclure le fichier d'interface dans plusieurs autres fichiers sans pollution. Maintenant, si vous pensez que ce fractionnement est une meilleure pratique (c'est le cas), alors la façon d'y parvenir en C est d'utiliser des fichiers d'en-tête.
gbjbaanb
Si les en-têtes étaient réellement utiles pour séparer l'interface et l'implémentation, des choses comme PImpl ne seraient pas nécessaires pour cacher les composants privés et découpler les utilisateurs finaux de devoir les recompiler. Au lieu de cela, nous obtenons le pire des deux mondes. Les en-têtes feignent une façade de séparation rapidement écartée, tout en ouvrant une myriade de pièges, qui nécessitent un code passe-partout verbeux si vous devez les contourner.
underscore_d
1

Je dirais en fait que les fichiers d'en-têtes ne sont pas géniaux car ils brouillent l'interface et l'implémentation. L'objectif de la programmation en général, et en particulier de la POO, est d'avoir une interface définie et de masquer les détails de l'implémentation, mais un fichier d'en-tête C ++ vous montre à la fois les méthodes, l'héritage et les membres publics (interface) ainsi que les méthodes privées et les membres privés (une partie de la mise en œuvre). Sans oublier que dans certains cas, vous finissez par insérer du code ou des constructeurs dans le fichier d'en-tête, et certaines bibliothèques incluent du code de modèle dans les en-têtes, ce qui mélange vraiment l'implémentation avec l'interface.

L'intention initiale, je crois, était de permettre au code d'utiliser d'autres bibliothèques, objets, etc. sans avoir à importer l'intégralité du contenu du script. Tout ce dont vous avez besoin est l'en-tête pour compiler et lier. Il économise du temps et des cycles de cette façon. Dans ce cas, c'est une idée décente, mais ce n'est qu'une façon de résoudre ces problèmes.

En ce qui concerne la navigation dans la structure du programme, la plupart des IDE offrent cette capacité, et il existe de nombreux outils qui lanceront les interfaces, effectueront l'analyse de code, la décompilation, etc. afin que vous puissiez voir ce qui se passe sous les couvertures.

Quant à savoir pourquoi d'autres langues n'implémentent pas la même fonctionnalité? Eh bien, parce que d'autres langues viennent d'autres personnes, et ces concepteurs / créateurs ont une vision différente de la façon dont les choses devraient fonctionner.

La meilleure réponse est de vous en tenir à ce que fait le travail que vous devez faire et de vous rendre heureux.

Maurice Reeves
la source
0

Dans de nombreux langages de programmation, lorsqu'un programme est subdivisé en plusieurs unités de compilation, le code dans une unité ne pourra utiliser des choses définies dans une autre que si le compilateur a des moyens de savoir ce que sont ces choses. Plutôt que d'exiger qu'un compilateur traitant une unité de compilation examine le texte entier de chaque unité de compilation qui définit tout ce qui est utilisé dans l'unité actuelle, il est préférable que le compilateur reçoive, pendant le traitement de chaque unité, le texte complet de l'unité à compiler avec avec un peu d'informations de tous les autres.

En C, le programmeur est responsable de la création de deux fichiers pour chaque unité - un contenant des informations qui ne seront nécessaires que lors de la compilation de cette unité, et une contenant des informations qui seront également nécessaires lors de la compilation d'autres unités. Il s'agit d'une conception plutôt méchante et hacky, mais elle évite d'avoir à définir une norme de langage spécifiant quoi que ce soit sur la façon dont les compilateurs doivent générer et traiter les fichiers intermédiaires. De nombreuses autres langues utilisent une approche différente, dans laquelle un compilateur générera à partir de chaque fichier source un fichier intermédiaire décrivant tout dans ce fichier source qui est censé être accessible au code extérieur. Cette approche évite d'avoir à dupliquer les informations dans deux fichiers source, mais elle nécessite qu'une plateforme de compilation ait défini la sémantique des fichiers d'une manière qui n'est pas requise pour C.

supercat
la source