Que doit contenir un fichier .h?


93

Lorsque vous divisez votre code en plusieurs fichiers, qu'est-ce qui devrait exactement entrer dans un fichier .h et que devrait-il entrer dans un fichier .cpp?



7
C'est un problème de style pur, mais je crois que les déclarations C ++ vont dans un .hppfichier tandis que les déclarations C vont dans un .hfichier. Ceci est très utile lors du mélange de code C et C ++ (par exemple des modules hérités en C).
Thomas Matthews

@ThomasMatthews Cela a du sens. Cette pratique est-elle souvent utilisée?
ty

@lightningleaf: Oui, la pratique est souvent utilisée en particulier lors du mélange de langages C ++ et C.
Thomas Matthews

Réponses:


113

Les fichiers d'en-tête ( .h) sont conçus pour fournir les informations qui seront nécessaires dans plusieurs fichiers. Des éléments tels que les déclarations de classe, les prototypes de fonctions et les énumérations se trouvent généralement dans les fichiers d'en-tête. En un mot, «définitions».

Les fichiers de code ( .cpp) sont conçus pour fournir les informations d'implémentation qui ne doivent être connues que dans un seul fichier. En général, les corps de fonctions et les variables internes qui ne devraient / ne seront jamais accessibles par d'autres modules, sont ce qui appartient aux .cppfichiers. En un mot, «implémentations».

La question la plus simple à se poser pour déterminer ce qui appartient à où est "si je change cela, devrai-je changer le code dans d'autres fichiers pour que les choses se compilent à nouveau?" Si la réponse est «oui», elle appartient probablement au fichier d'en-tête; si la réponse est «non», elle appartient probablement au fichier de code.


4
Sauf que les données de classe privée doivent aller dans l'en-tête. Les modèles doivent être complètement définis par l'en-tête (sauf si vous utilisez l'un des rares compilateurs qui prend en charge export). Le seul moyen de contourner le n ° 1 est PIMPL. # 2 serait possible s'il exportétait pris en charge et pourrait être possible en utilisant C ++ 0x et des externmodèles. OMI, les fichiers d'en-tête en C ++ perdent beaucoup de leur utilité.
KitsuneYMG

23
Tout va bien, mais avec une terminologie inexacte. En un mot, «déclarations» - le terme «définition» est synonyme de «mise en œuvre». Seuls le code déclaratif, le code en ligne, les définitions de macro et le code de modèle doivent figurer dans un en-tête; c'est-à-dire rien qui instancie le code ou les données.
Clifford

8
Je suis d'accord avec Clifford. Vous utilisez les termes déclaration et définition de manière assez vague et quelque peu interchangeable. Mais ils ont des significations précises en C ++. Exemples: une déclaration de classe introduit le nom d'une classe mais ne dit pas ce qu'elle contient. Une définition de classe répertorie tous les membres et fonctions d'amis. Les deux peuvent être placés dans des fichiers d'en-tête sans problème. Ce que vous appelez "prototype de fonction" est une déclaration de fonction . Mais une définition de fonction est cette chose qui contient le code de la fonction et doit être placée dans un fichier cpp - à moins qu'elle ne soit en ligne ou (faisant partie d'un) modèle.
sellibitze

5
Ils ont des significations précises en C ++, ils n'ont pas de significations précises en anglais. Ma réponse était écrite dans ce dernier.
Ambre

54

Le fait est qu'en C ++, c'est un peu plus compliqué que l'organisation en-tête / source C.

Que voit le compilateur?

Le compilateur voit un gros fichier source (.cpp) avec ses en-têtes correctement inclus. Le fichier source est l'unité de compilation qui sera compilée dans un fichier objet.

Alors, pourquoi les en-têtes sont-ils nécessaires?

Parce qu'une unité de compilation peut avoir besoin d'informations sur une implémentation dans une autre unité de compilation. On peut donc écrire par exemple l'implémentation d'une fonction dans une source, et écrire la déclaration de cette fonction dans une autre source ayant besoin de l'utiliser.

Dans ce cas, il existe deux copies des mêmes informations. Ce qui est mal ...

La solution est de partager quelques détails. Alors que l'implémentation doit rester dans la source, la déclaration de symboles partagés, comme les fonctions, ou la définition de structures, classes, énumérations, etc., pourrait devoir être partagée.

Les en-têtes sont utilisés pour mettre ces détails partagés.

Déplacez vers l'en-tête les déclarations de ce qui doit être partagé entre plusieurs sources

Rien de plus?

En C ++, il y a d'autres choses qui pourraient être placées dans l'en-tête car elles doivent aussi être partagées:

  • code en ligne
  • modèles
  • constantes (généralement celles que vous souhaitez utiliser à l'intérieur des commutateurs ...)

Déplacer vers l'en-tête TOUT ce qui doit être partagé, y compris les implémentations partagées

Cela signifie-t-il alors qu'il pourrait y avoir des sources à l'intérieur des en-têtes?

Oui. En fait, il y a beaucoup de choses différentes qui pourraient être à l'intérieur d'un "en-tête" (c'est-à-dire partagées entre les sources).

  • Déclarations prospectives
  • déclarations / définition de fonctions / structs / classes / templates
  • implémentation de code en ligne et basé sur des modèles

Cela devient compliqué, et dans certains cas (dépendances circulaires entre symboles), impossible de le conserver dans un en-tête.

Les en-têtes peuvent être divisés en trois parties

Cela signifie que, dans un cas extrême, vous pourriez avoir:

  • un en-tête de déclaration avant
  • un en-tête de déclaration / définition
  • un en-tête d'implémentation
  • une source d'implémentation

Imaginons que nous ayons un MyObject basé sur un modèle. Nous pourrions avoir:

// - - - - MyObject_forward.hpp - - - - 
// This header is included by the code which need to know MyObject
// does exist, but nothing more.
template<typename T>
class MyObject ;

.

// - - - - MyObject_declaration.hpp - - - - 
// This header is included by the code which need to know how
// MyObject is defined, but nothing more.
#include <MyObject_forward.hpp>

template<typename T>
class MyObject
{
   public :
      MyObject() ;
   // Etc.
} ;

void doSomething() ;

.

// - - - - MyObject_implementation.hpp - - - - 
// This header is included by the code which need to see
// the implementation of the methods/functions of MyObject,
// but nothing more.
#include <MyObject_declaration.hpp>

template<typename T>
MyObject<T>::MyObject()
{
   doSomething() ;
}

// etc.

.

// - - - - MyObject_source.cpp - - - - 
// This source will have implementation that does not need to
// be shared, which, for templated code, usually means nothing...
#include <MyObject_implementation.hpp>

void doSomething()
{
   // etc.
} ;

// etc.

Hou la la!

Dans la «vraie vie», c'est généralement moins compliqué. La plupart du code n'aura qu'un simple en-tête / organisation source, avec du code incorporé dans la source.

Mais dans d'autres cas (objets basés sur des modèles se connaissant les uns les autres), je devais avoir pour chaque objet des en-têtes de déclaration et d'implémentation séparés, avec une source vide comprenant ces en-têtes juste pour m'aider à voir certaines erreurs de compilation.

Une autre raison de décomposer les en-têtes en en-têtes séparés pourrait être d'accélérer la compilation, de limiter la quantité de symboles analysés au strict nécessaire et d'éviter la recompilation inutile d'une source qui ne se soucie que de la déclaration directe lorsqu'une implémentation de méthode en ligne a changé.

Conclusion

Vous devez rendre l'organisation de votre code aussi simple que possible et aussi modulaire que possible. Mettez autant que possible dans le fichier source. N'exposez dans les en-têtes que ce qui doit être partagé.

Mais le jour où vous aurez des dépendances circulaires entre des objets modèles, ne soyez pas surpris si l'organisation de votre code devient un peu plus "intéressante" que l'organisation simple en-tête / source ...

^ _ ^


17

en plus de toutes les autres réponses, je vous dirai ce que vous NE placez PAS dans un fichier d'en-tête: la
usingdéclaration (l'être le plus courant using namespace std;) ne doit pas apparaître dans un fichier d'en-tête car elle pollue l'espace de noms du fichier source dans lequel elle est incluse .


+1 avec une mise en garde que vous pouvez faire en utilisant tant qu'il se trouve dans un espace de noms de détail (ou un espace de noms anonyme). Mais oui, ne jamais utiliser usingpour importer des éléments dans un espace de noms global dans un en-tête.
KitsuneYMG

+1 Celui-ci est beaucoup plus facile à répondre. :) De plus, les fichiers d'en-tête ne doivent pas contenir d' espaces de noms anonymes .
sellibitze

Il est bien que les fichiers d'en-tête contiennent des espaces de noms anonymes, tant que vous comprenez ce que cela signifie, c'est-à-dire que chaque unité de traduction aura une copie différente des éléments que vous définissez l'espace de noms. Les fonctions en ligne dans les espaces de noms anonymes sont recommandées en C ++ pour les cas où vous les utiliseriez static inlineen C99, en raison de quelque chose à voir avec ce qui se passe lorsque vous combinez une liaison interne avec des modèles. Les espaces de noms Anon vous permettent de «masquer» les fonctions, tout en préservant les liens externes.
Steve Jessop

Steve, ce que tu as écrit ne m'a pas convaincu. Veuillez choisir un exemple concret où vous pensez qu'un espace de noms anon a tout son sens dans un fichier d'en-tête.
sellibitze

6

Ce qui se compile en rien (zéro empreinte binaire) entre dans le fichier d'en-tête.

Les variables ne se compilent pas en rien, mais les déclarations de type le font (car elles ne décrivent que le comportement des variables).

les fonctions ne le font pas, mais les fonctions en ligne le font (ou les macros), car elles produisent du code uniquement là où elles sont appelées.

les modèles ne sont pas du code, ils ne sont qu'une recette pour créer du code. donc ils vont également dans les fichiers h.


1
"les fonctions en ligne ... produisent du code uniquement là où elles sont appelées". Ce n'est pas vrai. les fonctions en ligne peuvent ou non être intégrées aux sites d'appels, mais même si elles sont intégrées, le corps de la fonction réelle existe toujours comme il le fait pour une fonction non en ligne. La raison pour laquelle il est acceptable d'avoir des fonctions en ligne dans les en-têtes n'a rien à voir avec le fait qu'elles génèrent du code, c'est parce que les fonctions en ligne ne déclenchent pas la règle d'une seule définition, donc contrairement aux fonctions non en ligne, il n'y a aucun problème à relier deux unités de traduction différentes qui ont tous deux inclus l'en-tête.
Steve Jessop

3

En général, vous placez les déclarations dans le fichier d'en-tête et les définitions dans le fichier d'implémentation (.cpp). L'exception à cela concerne les modèles, où la définition doit également figurer dans l'en-tête.

Cette question et d'autres similaires ont été fréquemment posées sur SO - voir Pourquoi des fichiers d'en-tête et des fichiers .cpp en C ++? et fichiers d'en-tête C ++, séparation de code par exemple.


bien sûr, vous pouvez également mettre des définitions de classe dans des fichiers d'en-tête. Ils n'ont même pas besoin d'être des modèles.
sellibitze

1

Vos déclarations de classe et de fonction ainsi que la documentation et les définitions des fonctions / méthodes en ligne (bien que certains préfèrent les mettre dans des fichiers .inl séparés).


1

Le fichier d'en-tête contient principalement un squelette de classe ou une déclaration (ne change pas fréquemment)

et le fichier cpp contient l' implémentation de classe (change fréquemment).


5
Veuillez vous abstenir d'utiliser une terminologie non standard. Qu'est-ce que "squelette de classe", qu'est-ce que "implémentation de classe"? De plus, ce que vous appelez la déclaration dans le contexte des classes inclut probablement des définitions de classe.
sellibitze

0

le fichier d'en-tête (.h) doit être destiné aux déclarations de classes, de structures et de ses méthodes, de prototypes, etc. L'implémentation de ces objets est faite dans cpp.

en .h

    class Foo {
    int j;

    Foo();
    Foo(int)
    void DoSomething();
}

0

Je m'attendrais à voir:

  • déclarations
  • commentaires
  • définitions marquées en ligne
  • modèles

la vraie réponse est cependant ce qu'il ne faut pas mettre:

  • définitions (peut conduire à une multiplication des choses)
  • en utilisant des déclarations / directives (les force sur n'importe qui, y compris votre en-tête, peut provoquer des barres obliques)

1
Vous pouvez également mettre des définitions de classe dans des fichiers d'en-tête. UNE déclaration de classe ne dit rien sur ses membres.
sellibitze

0

L'en-tête définit quelque chose mais ne dit rien sur l'implémentation. (Hors modèles dans ce "metafore".

Cela dit, vous devez diviser les «définitions» en sous-groupes, il existe, dans ce cas, deux types de définitions.

  • Vous définissez la "mise en page" de votre structure, en indiquant uniquement ce qui est nécessaire aux groupes d'utilisation environnants.
  • Les définitions d'une variable, d'une fonction et d'une classe.

Maintenant, je parle bien sûr du premier sous-groupe.

L'en-tête est là pour définir la disposition de votre structure afin d'aider le reste du logiciel à utiliser l'implémentation. Vous voudrez peut-être le voir comme une "abstraction" de votre implémentation, ce qui est dit méchamment mais, je pense que cela convient assez bien dans ce cas.

Comme les affiches précédentes l'ont dit et montré que vous déclarez les zones d'utilisation privées et publiques et leurs en-têtes, cela inclut également les variables privées et publiques. Maintenant, je ne veux pas entrer dans la conception du code ici, mais vous voudrez peut-être considérer ce que vous mettez dans vos en-têtes, car c'est la couche entre l'utilisateur final et l'implémentation.


0
  • Fichiers d'en-tête - ne devraient pas changer trop souvent pendant le développement -> vous devriez réfléchir et les écrire immédiatement (dans le cas idéal)
  • Fichiers source - changements pendant la mise en œuvre

C'est une pratique. Pour certains petits projets, c'est peut-être la voie à suivre. Mais vous pouvez essayer de désapprouver les fonctions et leurs prototypes (dans les fichiers d'en-tête), plutôt que de changer leur signature ou de les supprimer. Au moins jusqu'à ce que le nombre majeur change. Comme lorsque la version 1.9.2 passe à la version 2.0.0 bêta.
TamusJRoyce

0

En-tête (.h)

  • Macros et inclut nécessaires pour les interfaces (aussi peu que possible)
  • La déclaration des fonctions et des classes
  • Documentation de l'interface
  • Déclaration des fonctions / méthodes en ligne, le cas échéant
  • extern aux variables globales (le cas échéant)

Corps (.cpp)

  • Reste des macros et comprend
  • Inclure l'en-tête du module
  • Définition des fonctions et méthodes
  • Variables globales (le cas échéant)

En règle générale, vous mettez la partie "partagée" du module sur le .h (la partie que les autres modules doivent pouvoir voir) et la partie "non partagée" sur le .cpp

PD: Oui, j'ai inclus des variables globales. Je les ai utilisés quelques fois et il est important de ne pas les définir sur les en-têtes, sinon vous aurez beaucoup de modules, chacun définissant sa propre variable.

EDIT: Modifié après le commentaire de David


En règle générale, le fichier .h doit contenir le moins d'inclusions possible et le fichier .cpp doit inclure les en-têtes dont il a besoin. Cela raccourcit les temps de compilation et ne pollue pas les espaces de noms.
David Thornley
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.