Qu'est-ce qui devrait et ne devrait pas être dans un fichier d'en-tête? [fermé]

71

Quelles choses ne devraient absolument jamais être incluses dans un fichier d'en-tête?

Si, par exemple, je travaille avec un format standard documenté comportant de nombreuses constantes, est-ce une bonne pratique de les définir dans un fichier d'en-tête (si j'écris un analyseur syntaxique pour ce format)?

Quelles fonctions devraient aller dans le fichier d'en-tête?
Quelles fonctions ne devraient pas?

Moshe Magnes
la source
1
Courte et sans douleur: définitions et déclinaisons nécessaires dans plusieurs modules.
ott--
21
Marquer cette question comme "trop ​​large" et clore la clôture est une surdose absolue de honte. Cette question demande exactement ce que je recherche - la Question est bien formée et pose une question très claire: quelles sont les meilleures pratiques? Si cela est "trop ​​large" pour le génie logiciel ... nous pourrions également fermer tout ce forum.
Gewure
TL; DR. Pour C ++, dans la quatrième édition du "Langage de programmation C ++" écrit par Bjarne Stroustrup (son créateur), dans la Section 15.2.2, il est décrit ce qu'un en-tête devrait contenir et ne devrait pas contenir. Je sais que vous avez associé la question à la question C, mais certains des conseils sont également applicables. Je pense que c'est une bonne question ...
Horro

Réponses:

57

Quoi mettre dans les en-têtes:

  • Ensemble minimal de #includedirectives nécessaires pour rendre l'en-tête compilable lorsque celui-ci est inclus dans un fichier source.
  • Définitions de symbole de préprocesseur des choses qui doivent être partagées et ne peuvent être accomplies que via le préprocesseur. Même en C, les symboles du préprocesseur sont mieux conservés au minimum.
  • Déclarations en aval des structures nécessaires pour rendre les définitions de structure, les prototypes de fonctions et les déclarations de variables globales compilables dans le corps de l'en-tête.
  • Définitions des structures de données et des énumérations partagées entre plusieurs fichiers source.
  • Déclarations pour les fonctions et les variables dont les définitions seront visibles par l'éditeur de liens.
  • Définitions de fonctions en ligne, mais prenez garde ici.

Ce qui n'appartient pas à un en-tête:

  • #includeDirectives gratuites . Ces inclusions gratuites entraînent une recompilation de choses qui n'ont pas besoin d'être recompilées et peuvent parfois empêcher un système de compiler. Ne créez pas de #includefichier dans un en-tête si l'en-tête lui-même n'a pas besoin de cet autre fichier d'en-tête.
  • Symboles de préprocesseur dont l'intention pourrait être accomplie par un mécanisme quelconque, autre que le préprocesseur.
  • Beaucoup de définitions de structure. Divisez-les dans des en-têtes séparés.
  • Définitions en ligne de fonctions nécessitant des fonctions supplémentaires #include, sujettes à modification ou trop grandes. Ces fonctions en ligne devraient avoir peu ou pas de sortants, et s’ils le sont, il devrait être localisé dans les éléments définis dans l’en-tête.

Qu'est-ce qui constitue l'ensemble minimal d' #includeénoncés?

Cela s'avère être une question non triviale. Une définition de TL; DR: un fichier d’en-tête doit inclure les fichiers d’en-tête qui définissent directement chacun des types directement utilisés dans ou déclarant directement chacune des fonctions utilisées dans le fichier d’en-tête en question, sans rien inclure d'autre. Un pointeur ou un type de référence C ++ ne constitue pas une utilisation directe. les références en aval sont préférées.

Il y a une place pour une #includedirective gratuite , et cela se fait dans un test automatisé. Pour chaque fichier d'en-tête d'un progiciel, je génère et compile automatiquement les éléments suivants:

#include "path/to/random/header_under_test"
int main () { return 0; }

La compilation doit être propre (c.-à-d. Exempte de tout avertissement ou erreur). Des avertissements ou des erreurs concernant des types incomplets ou inconnus signifient que le fichier d’en-tête à tester comporte des #includedirectives manquantes et / ou des déclarations de transfert manquantes. Remarque: le fait que le test réussisse ne signifie pas que le jeu de #includedirectives est suffisant, encore moins minimal.

David Hammen
la source
Donc, si j'ai une bibliothèque qui définit une structure, appelée A, et que cette bibliothèque, appelée B, utilise cette structure, et que la bibliothèque B est utilisée par le programme C, devrais-je inclure le fichier d'en-tête de la bibliothèque A dans l'en-tête principal de la bibliothèque B ou Je viens de le déclarer avant? la bibliothèque A est compilée et liée à la bibliothèque B lors de sa compilation.
MarcusJ
@MarcusJ - La première chose que j'ai énumérée sous Ce qui n'appartient pas à un en-tête était les instructions gratuites #include. Si le fichier d'en-tête B ne dépend pas des définitions du fichier d'en-tête A, n'incluez pas #include le fichier d'en-tête A dans le fichier d'en-tête B. Un fichier d'en-tête n'est pas l'endroit idéal pour spécifier des dépendances de tiers ou des instructions de construction. Ceux-ci vont ailleurs, comme un fichier Lisez-moi de niveau supérieur.
David Hammen
1
@MarcusJ - J'ai mis à jour ma réponse pour tenter de répondre à votre question. Notez qu'il n'y a pas une réponse à votre question. Je vais illustrer par quelques extrêmes. Cas 1: Le seul endroit où la bibliothèque B utilise directement les fonctionnalités de la bibliothèque A se trouve dans les fichiers source de la bibliothèque B. Cas 2: La bibliothèque B est une mince extension de la fonctionnalité de la bibliothèque A, le ou les fichiers d’en-tête de la bibliothèque B utilisant directement les types et / ou les fonctions définis dans la bibliothèque A. Dans le cas 1, il n’ya aucune raison d’exposer la bibliothèque A dans les en-têtes de la bibliothèque B. Dans le cas 2, cette exposition est à peu près obligatoire.
David Hammen
Oui, c'est la deuxième affaire. Désolé, mon commentaire a ignoré le fait qu'il utilise des types déclarés dans la bibliothèque A dans les en-têtes de la bibliothèque B. Je pensais pouvoir faire une déclaration, mais je ne pense pas que cela va fonctionner. Merci pour la mise à jour.
MarcusJ
L'ajout de constantes à un fichier d'en-tête est-il un gros non-non?
mding5692
15

En plus de ce qui a déjà été dit.

Les fichiers H doivent toujours contenir:

  • Documentation du code source !!! Au minimum, à quoi servent les différents paramètres et les valeurs de retour des fonctions.
  • En-têtes, #ifndef MYHEADER_H #define MYHEADER_H ... #endif

Les fichiers H ne doivent jamais contenir:

  • Toute forme d'allocation de données.
  • Définitions de fonction. Les fonctions en ligne peuvent constituer une exception rare dans certains cas.
  • Quelque chose étiqueté static.
  • Typedefs, #defines ou constantes qui n'ont aucune pertinence pour le reste de l'application.

(Je dirais également qu'il n'y a jamais de raison d'utiliser des variables globales / externes non constantes, où que ce soit, mais c'est une discussion pour un autre article.)


la source
1
Je suis d'accord avec tout, sauf ce que vous avez souligné. Si vous faites une bibliothèque, oui, vous devriez documenter ou les utilisateurs de votre bibliothèque. Pour un projet interne, vous ne devriez pas avoir à encombrer vos en-têtes de documentation, si vous utilisez de bons noms de variables et de fonctions explicites.
martiert
5
@martiert Je suis aussi de l'école "laissez le code parler pour lui-même". Pourtant, vous devriez au moins toujours documenter vos fonctions, même si personne d'autre que vous ne les utilisera. Les choses qui présentent un intérêt particulier sont les suivantes: dans le cas où la fonction a une gestion des erreurs, quels codes d'erreur est renvoyée et dans quelles conditions est-elle défaillante? Qu'advient-il des paramètres (tampons, pointeurs, etc.) si la fonction échoue? Une autre chose très pertinente est la suivante: les paramètres de pointeur renvoient-ils quelque chose à l'appelant, c'est-à-dire attendent-ils de la mémoire allouée? ->
1
Le demandeur doit savoir quel traitement des erreurs est effectué dans la fonction et ce qui ne l’est pas. Si la fonction s'attend à une mémoire tampon allouée, elle laissera très probablement également les contrôles hors limites à l'appelant. Si la fonction s'appuie sur une autre fonction à exécuter, vous devez le documenter (par exemple, exécutez link_list_init () avant link_list_add ()). Et enfin, si la fonction a un "effet secondaire" tel que la création de fichiers, de threads, de minuteries ou autre, cela devrait être indiqué dans la documentation. ->
1
Peut-être que "documentation du code source" est trop large ici, cela appartient vraiment au code source. La "documentation d'utilisation" avec les entrées et les sorties, les pré et post-conditions et les effets secondaires devrait absolument y aller, pas de façon épique mais sous une forme brève .
Sécurisé
2
Un peu tardif, mais +1 pour la documentation. Pourquoi cette classe existe-t-elle? Le code ne parle pas pour lui-même. Que fait cette fonction? RTFC (lisez le fichier .cpp) est un acronyme obscène de quatre lettres. On ne devrait jamais avoir à RTFC pour comprendre. Le prototype dans l'en-tête doit résumer, dans un commentaire extractible (par exemple, doxygen), quels sont les arguments et ce que fait la fonction. Pourquoi ce membre de données existe-t-il, que contient-il et sa valeur est-elle exprimée en mètres, en pieds ou en longueurs? Cela aussi est un autre sujet pour les commentaires (extractibles) existent.
David Hammen
4

Je ne dirais probablement jamais jamais, mais les instructions qui génèrent des données et du code lorsqu’elles sont analysées ne doivent pas figurer dans un fichier .h.

Les macros, les fonctions en ligne et les modèles peuvent ressembler à des données ou à du code, mais ils ne génèrent pas de code lors de leur analyse, mais plutôt lorsqu'ils sont utilisés. Ces éléments doivent souvent être utilisés dans plus d'un fichier .c ou .cpp, ils appartiennent donc au fichier .h.

À mon avis, un fichier d'en-tête devrait avoir l'interface pratique minimale avec un .c ou .cpp correspondant. L'interface peut inclure #defines, class, typedef, définitions de structure, prototypes de fonctions et définitions externes moins préférées pour les variables globales. Toutefois, si une déclaration est utilisée dans un seul fichier source, elle devrait probablement être exclue du fichier .h et figurer à la place dans le fichier source.

Certains peuvent ne pas être d'accord, mais mes critères personnels pour les fichiers .h sont qu'ils incluent tous les autres fichiers .h dont ils ont besoin pour pouvoir compiler. Dans certains cas, il peut s'agir de nombreux fichiers. Nous disposons donc de méthodes efficaces pour réduire les dépendances externes, telles que les déclarations forward aux classes, qui nous permettent d'utiliser des pointeurs sur les objets d'une classe sans inclure ce qui pourrait être une grande arborescence de fichiers d'inclusion.

DeveloperDon
la source
3

Le fichier d'en-tête doit avoir l'organisation suivante:

  • définitions de type et de constante
  • déclarations d'objets externes
  • déclarations de fonctions externes

Les fichiers d'en-tête ne doivent jamais contenir de définitions d'objet, mais uniquement des définitions de type et des déclarations d'objet.

leD
la source
Qu'en est-il des définitions de fonctions en ligne?
Kos
Si la fonction en ligne est une fonction «d'assistance» utilisée uniquement dans un module C, placez-la uniquement dans ce fichier .c. Si la fonction inline doit être visible pour deux modules ou plus, insérez-la dans le fichier d'en-tête.
theD
De même, si la fonction doit être visible au-delà des limites d'une bibliothèque, ne la mettez pas en ligne car cela obligerait tous ceux qui l'utilisent à recompiler chaque fois que vous modifiez des éléments.
Donal Fellows
@DonalFellows: C'est une solution en retour. Une meilleure règle: ne mettez pas de contenu dans les en-têtes qui sont sujets à de fréquentes modifications. Il n'y a rien de mal à insérer une petite fonction courte dans un en-tête si la fonction n'a pas de fanout et a une définition claire qui ne changera que si la structure de données sous-jacente change. Si la définition de la fonction change parce que la définition de la structure sous-jacente a été modifiée, vous devez tout recompiler, mais vous devrez quand même le faire, car la définition de la structure a changé.
David Hammen
0

Les instructions qui génèrent des données et du code au fur et à mesure de leur analyse ne doivent pas figurer dans un .hfichier. En ce qui concerne mon point de vue, un fichier d’en-tête ne devrait avoir que l’interface pratique minimale avec un .cou .cpp.

Ajay Prasad
la source