Je n'ai jamais vraiment compris pourquoi C ++ a besoin d'un fichier d'en-tête séparé avec les mêmes fonctions que dans le fichier .cpp. Cela rend la création de classes et leur refactorisation très difficiles, et cela ajoute des fichiers inutiles au projet. Et puis il y a le problème de devoir inclure des fichiers d'en-tête, mais de vérifier explicitement s'ils ont déjà été inclus.
C ++ a été ratifié en 1998, alors pourquoi est-il conçu de cette façon? Quels sont les avantages d'avoir un fichier d'en-tête séparé?
Question complémentaire:
Comment le compilateur trouve-t-il le fichier .cpp avec le code, alors que tout ce que j'inclus est le fichier .h? Suppose-t-il que le fichier .cpp porte le même nom que le fichier .h ou examine-t-il réellement tous les fichiers de l'arborescence de répertoires?
la source
Réponses:
Vous semblez demander comment séparer les définitions des déclarations, bien qu'il existe d'autres utilisations des fichiers d'en-tête.
La réponse est que C ++ n'a pas "besoin" de cela. Si vous marquez tout en ligne (ce qui est de toute façon automatique pour les fonctions membres définies dans une définition de classe), alors il n'y a pas besoin de séparation. Vous pouvez simplement tout définir dans les fichiers d'en-tête.
Les raisons pour lesquelles vous pourriez vouloir vous séparer sont:
Si votre question plus générale est «pourquoi le C ++ n'est-il pas identique à Java?», Alors je dois vous demander: «pourquoi écrivez-vous C ++ au lieu de Java? ;-p
Plus sérieusement, cependant, la raison est que le compilateur C ++ ne peut pas simplement accéder à une autre unité de traduction et comprendre comment utiliser ses symboles, de la manière que javac peut et fait. Le fichier d'en-tête est nécessaire pour déclarer au compilateur ce qu'il peut espérer être disponible au moment de la liaison.
Il en
#include
va de même pour une substitution textuelle directe. Si vous définissez tout dans les fichiers d'en-tête, le préprocesseur finit par créer un énorme copier-coller de chaque fichier source de votre projet, et l'introduire dans le compilateur. Le fait que la norme C ++ ait été ratifiée en 1998 n'a rien à voir avec cela, c'est le fait que l'environnement de compilation pour C ++ est si étroitement basé sur celui de C.Conversion de mes commentaires pour répondre à votre question de suivi:
Ce n'est pas le cas, du moins pas au moment où il compile le code qui utilisait le fichier d'en-tête. Les fonctions que vous liez n'ont même pas besoin d'avoir été écrites encore, peu importe que le compilateur sache dans quel
.cpp
fichier elles se trouveront. Tout ce que le code appelant doit savoir au moment de la compilation est exprimé dans la déclaration de fonction. Au moment de la liaison, vous fournissez une liste de.o
fichiers, ou de bibliothèques statiques ou dynamiques, et l'en-tête en fait est une promesse que les définitions des fonctions seront là quelque part.la source
C ++ le fait de cette façon parce que C l'a fait de cette façon, donc la vraie question est de savoir pourquoi C l'a fait de cette façon? Wikipédia en parle un peu.
la source
Certaines personnes considèrent les fichiers d'en-tête comme un avantage:
En fin de compte, le système d'en-tête est un artefact des années 70 lorsque C a été conçu. À l'époque, les ordinateurs avaient très peu de mémoire et garder le module entier en mémoire n'était tout simplement pas une option. Un compilateur devait commencer à lire le fichier en haut, puis procéder linéairement à travers le code source. Le mécanisme d'en-tête permet cela. Le compilateur n'a pas besoin de prendre en compte d'autres unités de traduction, il doit simplement lire le code de haut en bas.
Et C ++ a conservé ce système pour une compatibilité ascendante.
Aujourd'hui, cela n'a aucun sens. Il est inefficace, sujet aux erreurs et trop compliqué. Il existe de bien meilleurs moyens de séparer l'interface et l'implémentation, si tel était l'objectif.
Cependant, l'une des propositions pour C ++ 0x était d'ajouter un système de modules approprié, permettant au code d'être compilé comme .NET ou Java, en modules plus grands, le tout en une seule fois et sans en-têtes. Cette proposition n'a pas fait la coupe dans C ++ 0x, mais je crois qu'elle est toujours dans la catégorie «nous aimerions faire ça plus tard». Peut-être dans un TR2 ou similaire.
la source
À ma compréhension (limitée - je ne suis pas un développeur C normalement), ceci est enraciné dans C. Rappelez-vous que C ne sait pas ce que sont les classes ou les espaces de noms, c'est juste un long programme. De plus, les fonctions doivent être déclarées avant de les utiliser.
Par exemple, ce qui suit doit donner une erreur du compilateur:
L'erreur devrait être que "SomeOtherFunction n'est pas déclaré" parce que vous l'appelez avant sa déclaration. Une façon de résoudre ce problème consiste à déplacer SomeOtherFunction au-dessus de SomeFunction. Une autre approche consiste à déclarer d'abord la signature des fonctions:
Cela permet au compilateur de savoir: Regardez quelque part dans le code, il y a une fonction appelée SomeOtherFunction qui renvoie void et ne prend aucun paramètre. Donc, si vous rencontrez du code qui tente d'appeler SomeOtherFunction, ne paniquez pas et allez plutôt le chercher.
Maintenant, imaginez que vous avez SomeFunction et SomeOtherFunction dans deux fichiers .c différents. Vous devez ensuite #inclure "SomeOther.c" dans Some.c. Maintenant, ajoutez quelques fonctions "privées" à SomeOther.c. Comme C ne connaît pas les fonctions privées, cette fonction serait également disponible dans Some.c.
C'est là que les fichiers .h entrent en jeu: ils spécifient toutes les fonctions (et variables) que vous souhaitez «exporter» à partir d'un fichier .c accessible dans d'autres fichiers .c. De cette façon, vous gagnez quelque chose comme une portée publique / privée. De plus, vous pouvez donner ce fichier .h à d'autres personnes sans avoir à partager votre code source - les fichiers .h fonctionnent également avec les fichiers .lib compilés.
Donc, la raison principale est vraiment pour la commodité, pour la protection du code source et pour avoir un peu de découplage entre les parties de votre application.
C'était C cependant. C ++ a introduit les classes et les modificateurs privés / publics, donc même si vous pouvez toujours demander s'ils sont nécessaires, C ++ AFAIK nécessite toujours la déclaration des fonctions avant de les utiliser. De plus, de nombreux développeurs C ++ sont ou étaient également des développeurs C et ont repris leurs concepts et leurs habitudes en C ++ - pourquoi changer ce qui n'est pas cassé?
la source
Premier avantage: si vous n'avez pas de fichiers d'en-tête, vous devrez inclure des fichiers source dans d'autres fichiers source. Cela entraînerait la recompilation des fichiers inclus lorsque le fichier inclus changerait.
Deuxième avantage: il permet de partager les interfaces sans partager le code entre différentes unités (différents développeurs, équipes, entreprises etc.)
la source
Le besoin de fichiers d'en-tête résulte des limitations que le compilateur a pour connaître les informations de type pour les fonctions et / ou les variables dans d'autres modules. Le programme ou la bibliothèque compilé n'inclut pas les informations de type requises par le compilateur pour se lier à des objets définis dans d'autres unités de compilation.
Afin de compenser cette limitation, C et C ++ autorisent les déclarations et ces déclarations peuvent être incluses dans des modules qui les utilisent à l'aide de la directive #include du préprocesseur.
Les langages comme Java ou C #, quant à eux, incluent les informations nécessaires pour la liaison dans la sortie du compilateur (fichier de classe ou assembly). Par conséquent, il n'est plus nécessaire de maintenir les déclarations autonomes à inclure par les clients d'un module.
La raison pour laquelle les informations de liaison ne sont pas incluses dans la sortie du compilateur est simple: elles ne sont pas nécessaires au moment de l'exécution (toute vérification de type se produit au moment de la compilation). Cela ne ferait que perdre de l'espace. N'oubliez pas que le C / C ++ vient d'une époque où la taille d'un exécutable ou d'une bibliothèque importait un peu.
la source
C ++ a été conçu pour ajouter des fonctionnalités de langage de programmation moderne à l'infrastructure C, sans rien changer inutilement à C qui ne concerne pas spécifiquement le langage lui-même.
Oui, à ce stade (10 ans après le premier standard C ++ et 20 ans après le début de son utilisation), il est facile de se demander pourquoi il n'a pas un système de modules approprié. De toute évidence, tout nouveau langage conçu aujourd'hui ne fonctionnerait pas comme C ++. Mais ce n'est pas le but du C ++.
Le but du C ++ est d'être évolutif, une continuation fluide de la pratique existante, en ajoutant uniquement de nouvelles capacités sans (trop souvent) casser des choses qui fonctionnent correctement pour sa communauté d'utilisateurs.
Cela signifie que cela rend certaines choses plus difficiles (en particulier pour les personnes qui démarrent un nouveau projet), et certaines choses plus faciles (en particulier pour ceux qui maintiennent le code existant) que d'autres langages.
Donc, plutôt que de s'attendre à ce que C ++ se transforme en C # (ce qui serait inutile car nous avons déjà C #), pourquoi ne pas simplement choisir le bon outil pour le travail? Moi-même, je m'efforce d'écrire des morceaux importants de nouvelles fonctionnalités dans un langage moderne (il se trouve que j'utilise C #), et j'ai une grande quantité de C ++ existant que je garde en C ++ car il n'y aurait aucune valeur réelle à le réécrire tout. Ils s'intègrent très bien de toute façon, donc c'est largement indolore.
la source
Eh bien, C ++ a été ratifié en 1998, mais il était utilisé depuis bien plus longtemps que cela, et la ratification fixait principalement l'utilisation actuelle plutôt qu'une structure imposante. Et comme C ++ était basé sur C et que C a des fichiers d'en-tête, C ++ les a aussi.
La principale raison des fichiers d'en-tête est de permettre la compilation séparée des fichiers et de minimiser les dépendances.
Disons que j'ai foo.cpp et que je souhaite utiliser le code des fichiers bar.h / bar.cpp.
Je peux #inclure "bar.h" dans foo.cpp, puis programmer et compiler foo.cpp même si bar.cpp n'existe pas. Le fichier d'en-tête agit comme une promesse au compilateur que les classes / fonctions de bar.h existeront au moment de l'exécution, et il a déjà tout ce qu'il doit savoir.
Bien sûr, si les fonctions de bar.h n'ont pas de corps lorsque j'essaye de lier mon programme, alors il ne liera pas et j'obtiendrai une erreur.
Un effet secondaire est que vous pouvez donner aux utilisateurs un fichier d'en-tête sans révéler votre code source.
Un autre est que si vous modifiez l'implémentation de votre code dans le fichier * .cpp, mais ne modifiez pas du tout l'en-tête, vous n'avez qu'à compiler le fichier * .cpp au lieu de tout ce qui l'utilise. Bien sûr, si vous mettez beaucoup d'implémentation dans le fichier d'en-tête, cela devient moins utile.
la source
Il n'a pas besoin d'un fichier d'en-tête séparé avec les mêmes fonctions que dans main. Il n'en a besoin que si vous développez une application utilisant plusieurs fichiers de code et si vous utilisez une fonction qui n'a pas été déclarée auparavant.
C'est vraiment un problème de portée.
la source
En fait, les fichiers d'en-tête deviennent très utiles lors de l'examen des programmes pour la première fois, l'extraction des fichiers d'en-tête (en utilisant uniquement un éditeur de texte) vous donne un aperçu de l'architecture du programme, contrairement à d'autres langages où vous devez utiliser des outils sophistiqués pour afficher les classes et leurs fonctions de membre.
la source
Je pense que la vraie raison (historique) derrière les fichiers d' en- tête a été fait comme facile pour les développeurs de compilateur ... mais, les fichiers en- tête ne donne des avantages.
Consultez ce post précédent pour plus de discussions ...
la source
Eh bien, vous pouvez parfaitement développer C ++ sans fichiers d'en-tête. En fait, certaines bibliothèques qui utilisent de manière intensive des modèles n'utilisent pas le paradigme des fichiers d'en-tête / code (voir boost). Mais en C / C ++, vous ne pouvez pas utiliser quelque chose qui n'est pas déclaré. Un moyen pratique de résoudre ce problème consiste à utiliser des fichiers d'en-tête. De plus, vous bénéficiez de l'avantage de partager l'interface sans partager le code / l'implémentation. Et je pense que cela n'a pas été envisagé par les créateurs du C: lorsque vous utilisez des fichiers d'en-tête partagés, vous devez utiliser le fameux:
ce n'est pas vraiment une fonctionnalité de langage, mais un moyen pratique de gérer l'inclusion multiple.
Donc, je pense que lorsque C a été créé, les problèmes de déclaration forward ont été sous-estimés et maintenant, lorsque nous utilisons un langage de haut niveau comme C ++, nous devons faire face à ce genre de choses.
Un autre fardeau pour nous, pauvres utilisateurs de C ++ ...
la source
Si vous voulez que le compilateur trouve automatiquement les symboles définis dans d'autres fichiers, vous devez forcer le programmeur à placer ces fichiers dans des emplacements prédéfinis (comme la structure des packages Java détermine la structure des dossiers du projet). Je préfère les fichiers d'en-tête. Vous aurez également besoin des sources des bibliothèques que vous utilisez ou d'un moyen uniforme de mettre les informations nécessaires au compilateur dans des binaires.
la source