Pourquoi avons-nous besoin d'inclure à la fois les .h
et les .cpp
fichiers pendant que nous pouvons le faire fonctionner uniquement en incluant le .cpp
fichier?
Par exemple: la création d' une file.h
déclaration contenant, puis en créant un file.cpp
contenant des définitions et comprenant à la fois dans main.cpp
.
En variante: la création d' une file.cpp
déclaration / définitions contenant (pas de prototypes) , y compris dans main.cpp
.
Les deux fonctionnent pour moi. Je ne vois pas la différence. Peut-être un aperçu de la compilation et la liaison peut aider.
Réponses:
Bien que vous puissiez inclure des
.cpp
fichiers comme vous l'avez mentionné, c'est une mauvaise idée.Comme vous l'avez mentionné, les déclarations appartiennent aux fichiers d'en-tête. Ceux-ci ne posent aucun problème lorsqu'ils sont inclus dans plusieurs unités de compilation car ils n'incluent pas d'implémentations. L'inclusion de plusieurs fois la définition d'une fonction ou d'un membre de classe causera normalement un problème (mais pas toujours) car l'éditeur de liens sera confus et générera une erreur.
Ce qui devrait arriver, c'est que chaque
.cpp
fichier comprend des définitions pour un sous-ensemble du programme, comme une classe, un groupe de fonctions organisé de manière logique, des variables statiques globales (à utiliser avec modération le cas échéant), etc.Chaque unité de compilation (
.cpp
fichier) comprend alors toutes les déclarations dont elle a besoin pour compiler les définitions qu'elle contient. Il garde une trace des fonctions et des classes qu'il référence mais ne contient pas, de sorte que l'éditeur de liens peut les résoudre plus tard lorsqu'il combine le code objet dans un exécutable ou une bibliothèque.Exemple
Foo.h
-> contient une déclaration (interface) pour la classe Foo.Foo.cpp
-> contient la définition (implémentation) de la classe Foo.Main.cpp
-> contient la méthode principale, le point d'entrée du programme. Ce code instancie un Foo et l'utilise.Les deux
Foo.cpp
etMain.cpp
doivent inclureFoo.h
.Foo.cpp
il a besoin parce qu'il définit le code qui sauvegarde l'interface de classe, donc il a besoin de savoir ce que cette interface est.Main.cpp
en a besoin car il crée un Foo et invoque son comportement, il doit donc savoir quel est ce comportement, la taille d'un Foo en mémoire et comment trouver ses fonctions, etc. mais il n'a pas encore besoin de l'implémentation réelle.Le compilateur générera à
Foo.o
partir deFoo.cpp
ce qui contient tout le code de la classe Foo sous forme compilée. Il génère égalementMain.o
qui inclut la méthode principale et des références non résolues à la classe Foo.Vient maintenant l'éditeur de liens, qui combine les deux fichiers objets
Foo.o
etMain.o
en un fichier exécutable. Il voit les références Foo non résolues dansMain.o
mais voit queFoo.o
contient les symboles nécessaires, il « relie les points » pour ainsi dire. Un appel de fonctionMain.o
est maintenant connecté à l'emplacement réel du code compilé de façon à l' exécution, le programme peut sauter au bon endroit.Si vous aviez inclus le
Foo.cpp
fichier dansMain.cpp
, il y aurait deux définitions de la classe Foo. L'éditeur de liens verrait cela et dirait "Je ne sais pas lequel choisir, c'est donc une erreur." L'étape de compilation réussirait, mais pas la liaison. (À moins que vous ne compiliez pas,Foo.cpp
mais pourquoi est-ce dans un.cpp
fichier séparé ?)Enfin, l'idée des différents types de fichiers est sans rapport avec un compilateur C / C. Il compile « fichiers texte » qui contiennent un code valide , espérons pour la langue souhaitée. Parfois , il peut être en mesure de dire la langue en fonction de l'extension du fichier. Par exemple, compiler un
.c
fichier avec aucune option de compilateur et il assumera C, tandis qu'une.cc
ou l'.cpp
extension raconteraient à assumer C ++. Cependant, je peux facilement dire à un compilateur de compiler un fichier.h
ou même.docx
en C ++, et il émettra un.o
fichier object ( ) s'il contient du code C ++ valide au format texte brut. Ces extensions sont plus au profit du programmeur. Si je voisFoo.h
etFoo.cpp
, je suppose tout de suite que la première contient la déclaration de la classe et la seconde contient la définition.la source
Foo.cpp
dansMain.cpp
vous n'avez pas besoin d'inclure le.h
fichier, vous avez un fichier de moins, vous gagnez toujours à diviser le code en fichiers séparés pour plus de lisibilité, et votre commande de compilation est plus simple. Même si je comprends l'importance des dossiers en- tête , je ne pense pas que ce soit ilIf you had included the Foo.cpp file in Main.cpp, there would be two definitions of class Foo.
Phrase la plus importante concernant la question.En savoir plus sur le rôle du préprocesseur C et C ++ , qui est conceptuellement la première "phase" du compilateur C ou C ++ (historiquement, c'était un programme distinct
/lib/cpp
; maintenant, pour des raisons de performances, il est intégré à l'intérieur du compilateur proprement ditcc1
oucc1plus
). Lisez en particulier la documentation ducpp
préprocesseur GNU . Donc, dans la pratique, le compilateur traite d'abord conceptuellement votre unité de compilation (ou unité de traduction ), puis travaille sur le formulaire prétraité.Vous devrez probablement toujours inclure le fichier d'en-tête
file.h
s'il contient (comme dicté par les conventions et les habitudes ):typedef
,struct
,class
etc, ...)static inline
fonctionsNotez que c'est une question de conventions (et de commodité) de les mettre dans un fichier d'en-tête.
Bien sûr, votre implémentation a
file.cpp
besoin de tout ce qui précède, donc le veut d'#include "file.h"
abord.Il s'agit d'une convention (mais très courante). Vous pouvez éviter les fichiers d'en-tête et copier et coller leur contenu dans des fichiers d'implémentation (c'est-à-dire des unités de traduction). Mais vous ne voulez pas cela (sauf peut-être si votre code C ou C ++ est généré automatiquement; alors vous pourriez faire en sorte que le programme générateur fasse ce copier-coller, imitant le rôle du préprocesseur).
Le fait est que le préprocesseur effectue uniquement des opérations textuelles . Vous pouvez (en principe) l'éviter entièrement par copier-coller, ou le remplacer par un autre "préprocesseur" ou générateur de code C (comme gpp ou m4 ).
Un problème supplémentaire est que les normes C (ou C ++) récentes définissent plusieurs en-têtes standard. La plupart des implémentations implémentent réellement ces en - têtes standard en tant que fichiers (spécifiques à l'implémentation) , mais je pense qu'il serait possible pour une implémentation conforme d'implémenter des inclusions standard (comme
#include <stdio.h>
pour C ou#include <vector>
pour C ++) avec quelques astuces magiques (par exemple en utilisant une base de données ou certains informations à l' intérieur du compilateur).Si vous utilisez des compilateurs GCC (par exemple
gcc
oug++
), vous pouvez utiliser l'-H
indicateur pour être informé de chaque inclusion, et les-C -E
indicateurs pour obtenir le formulaire prétraité. Bien sûr, il existe de nombreux autres drapeaux du compilateur affectant le prétraitement (par exemple-I /some/dir/
pour ajouter/some/dir/
pour rechercher les fichiers inclus, et-D
pour prédéfinir une macro de préprocesseur, etc, etc ....).NB. Les futures versions de C ++ (peut-être C ++ 20 , peut-être même plus tard) pourraient avoir des modules C ++ .
la source
En raison du modèle de construction à plusieurs unités de C ++, vous avez besoin d'un moyen pour avoir du code qui apparaît dans votre programme une seule fois (définitions), et vous avez besoin d'un moyen d'avoir du code qui apparaît dans chaque unité de traduction de votre programme (déclarations).
De là naît l'idiome d'en-tête C ++. C'est une convention pour une raison.
Vous pouvez sauvegarder tout votre programme dans une seule unité de traduction, mais cela pose des problèmes de réutilisation du code, de test unitaire et de gestion des dépendances entre modules. Il est également juste un grand désordre.
la source
La réponse sélectionnée de Pourquoi avons-nous besoin d'écrire un fichier d'en-tête? est une explication raisonnable, mais je voulais ajouter des détails supplémentaires.
Il semble que le rationnel des fichiers d'en-tête a tendance à se perdre dans l'enseignement et la discussion du C / C ++.
En-têtes fichier fournir une solution à deux problèmes de développement d'applications:
C / C ++ peut évoluer de petits programmes à de très gros programmes de plusieurs millions de lignes et plusieurs milliers de fichiers. Le développement d'applications peut évoluer d'équipes d'un développeur à des centaines de développeurs.
Vous pouvez porter plusieurs chapeaux en tant que développeur. En particulier, vous pouvez être l'utilisateur d'une interface pour des fonctions et des classes, ou vous pouvez être l'auteur d'une interface de fonctions et de classes.
Lorsque vous utilisez une fonction, vous devez connaître l'interface de la fonction, quels paramètres utiliser, ce que les fonctions renvoient et vous devez savoir ce que fait la fonction. Ceci est facilement documenté dans le fichier d'en-tête sans jamais regarder l'implémentation. Quand avez-vous lu la mise en œuvre de
printf
? Achetez nous l'utilisons tous les jours.Lorsque vous êtes le développeur d'une interface, le chapeau change de direction. Le fichier d'en-tête fournit la déclaration de l'interface publique. Le fichier d'en-tête définit ce dont une autre implémentation a besoin pour utiliser cette interface. Les informations internes et privées à cette nouvelle interface ne sont pas (et ne doivent pas) être déclarées dans le fichier d'en-tête. Le fichier d'en-tête public devrait être tout ce dont tout le monde a besoin pour utiliser le module.
Pour un développement à grande échelle, la compilation et la liaison peuvent prendre beaucoup de temps. De plusieurs minutes à plusieurs heures (voire à plusieurs jours!). La division du logiciel en interfaces (en-têtes) et implémentations (sources) fournit une méthode pour compiler uniquement les fichiers qui doivent être compilés plutôt que de tout reconstruire.
De plus, les fichiers d'en-tête permettent à un développeur de fournir une bibliothèque (déjà compilée) ainsi qu'un fichier d'en-tête. Les autres utilisateurs de la bibliothèque peuvent ne jamais voir l'implémentation appropriée, mais peuvent toujours utiliser la bibliothèque avec le fichier d'en-tête. Vous faites cela tous les jours avec la bibliothèque standard C / C ++.
Même si vous développez une petite application, l'utilisation de techniques de développement logiciel à grande échelle est une bonne habitude. Mais nous devons également nous rappeler pourquoi nous utilisons ces habitudes.
la source
Vous pourriez tout aussi bien demander pourquoi ne pas simplement mettre tout votre code dans un seul fichier.
La réponse la plus simple est la maintenance du code.
Il y a des moments où il est raisonnable de créer une classe:
Le moment où il est raisonnable de s'aligner totalement dans un en-tête est lorsque la classe est vraiment une structure de données avec quelques getters et setters de base et peut-être un constructeur qui prend des valeurs pour initialiser ses membres.
(Les modèles, qui doivent tous être alignés, sont un problème légèrement différent).
L'autre fois où vous créez une classe dans un en-tête, c'est quand vous pouvez utiliser la classe dans plusieurs projets et que vous voulez spécifiquement éviter de créer des liens dans les bibliothèques.
Un moment où vous pouvez inclure une classe entière dans une unité de compilation et ne pas exposer du tout son en-tête est:
Une classe "impl" qui n'est utilisée que par la classe qu'elle implémente. Il s'agit d'un détail d'implémentation de cette classe et n'est pas utilisé en externe.
Une implémentation d'une classe de base abstraite qui est créée par une sorte de méthode d'usine qui renvoie un pointeur / référence / pointeur intelligent) à la classe de base. La méthode d'usine serait exposée par la classe elle-même ne le serait pas. (De plus, si la classe a une instance qui s'enregistre sur une table via une instance statique, elle n'a même pas besoin d'être exposée via une fabrique).
Une classe de type "foncteur".
En d'autres termes, lorsque vous ne voulez pas que quelqu'un inclue l'en-tête.
Je sais ce que vous pensez peut-être ... Si c'est juste pour la maintenabilité, en incluant le fichier cpp (ou un en-tête totalement intégré) vous pouvez facilement éditer un fichier pour "trouver" le code et simplement reconstruire.
Cependant, la "maintenabilité" ne consiste pas seulement à avoir un code bien rangé. C'est une question d'impacts du changement. Il est généralement connu que si vous conservez un en-tête inchangé et que vous modifiez simplement un
(.cpp)
fichier d' implémentation , il ne sera pas nécessaire de reconstruire l'autre source car il ne devrait pas y avoir d'effets secondaires.Cela rend «plus sûr» d'effectuer de tels changements sans se soucier de l'effet d'entraînement et c'est ce que l'on entend vraiment par «maintenabilité».
la source
L'exemple de Snowman a besoin d'une petite extension pour vous montrer exactement pourquoi les fichiers .h sont nécessaires.
Ajoutez une autre barre de classe dans le jeu qui dépend également de la classe Foo.
Foo.h -> contient une déclaration pour la classe Foo
Foo.cpp -> contient la définition (impementation) de la classe Foo
Main.cpp -> utilise une variable de type Foo.
Bar.cpp -> utilise également une variable de type Foo.
Maintenant, tous les fichiers cpp doivent inclure Foo.h Ce serait une erreur d'inclure Foo.cpp dans plus d'un autre fichier cpp. L'éditeur de liens échouerait car la classe Foo serait définie plusieurs fois.
la source
.h
fichiers - avec les gardes d'inclusion et c'est exactement le même problème que vous avez avec les.h
fichiers de toute façon. Ce n'est pas du tout ce que.h
sont les fichiers..cpp
fichier.Si vous écrivez tout le code dans le même fichier, cela rendra votre code laid.
Deuxièmement, vous ne pourrez pas partager votre classe écrite avec d'autres. Selon l'ingénierie logicielle, vous devez écrire le code client séparément. Le client ne doit pas savoir comment fonctionne votre programme. Ils ont juste besoin de sortie. Si vous écrivez tout le programme dans le même fichier, la sécurité de votre programme sera compromise.
la source