web-dev-qa-db-fra.com

Pourquoi utiliser #ifndef CLASS_H et #define CLASS_H dans un fichier .h mais pas dans un fichier .cpp?

J'ai toujours vu des gens écrire

class.h

#ifndef CLASS_H
#define CLASS_H

//blah blah blah

#endif

La question qui se pose est de savoir pourquoi ils ne le font pas également pour le fichier .cpp qui contient les définitions des fonctions de classe.

Disons que j'ai main.cpp Et que main.cpp Inclut class.h. Le fichier class.h N'importe rien, alors comment main.cpp Sait ce qu'il y a dans le fichier class.cpp?

125
user385261

Tout d'abord, pour répondre à votre première demande:

Lorsque vous voyez ceci dans le fichier . H:

#ifndef FILE_H
#define FILE_H

/* ... Declarations etc here ... */

#endif

Il s'agit d'une technique de préprocesseur consistant à empêcher l'inclusion multiple d'un fichier d'en-tête, ce qui peut poser problème pour diverses raisons. Lors de la compilation de votre projet, chaque fichier . Cpp (généralement) est compilé. En termes simples, cela signifie que le compilateur prendra votre fichier . Cpp, ouvrira tous les fichiers #included par cela, les concaténer dans un fichier texte massif, puis effectuer une analyse syntaxique et enfin le convertir en un code intermédiaire, optimiser/effectuer d'autres tâches et enfin générer la sortie Assembly pour l'architecture cible. Pour cette raison, si un fichier est #included plusieurs fois sous un fichier . cpp, le compilateur ajoutera son contenu deux fois. Par conséquent, s’il contient des définitions, vous obtiendrez une erreur du compilateur vous indiquant que vous avez redéfini une variable. Lorsque le fichier est traité par l’étape du préprocesseur dans le processus de compilation, les deux premières lignes vérifient si FILE_H a été défini pour le préprocesseur. Sinon, cela définira FILE_H et continuez le traitement du code entre elle et le #endif directive. La prochaine fois que le préprocesseur verra le contenu de ce fichier, la vérification par rapport à FILE_H sera faux, donc il analysera immédiatement le #endif et continue après. Cela évite les erreurs de redéfinition.

Et pour répondre à votre deuxième préoccupation:

En programmation C++, en règle générale, nous séparons le développement en deux types de fichiers. On est avec une extension de . H et on appelle cela un "fichier en-tête". Ils fournissent généralement une déclaration de fonctions, de classes, de structures, de variables globales, de typedefs, de macros de pré-traitement et de définitions, etc. Ils vous fournissent simplement des informations sur votre code. Ensuite, nous avons l'extension . Cpp que nous appelons un "fichier de code". Cela fournira des définitions pour ces fonctions, membres de classe, tous membres de structure qui ont besoin de définitions, variables globales, etc. Ainsi, le fichier . H déclare le code et le fichier . Cpp met en œuvre cette déclaration. Pour cette raison, nous compilons généralement chaque fichier . Cpp en un objet lors de la compilation, puis lions ces objets (car vous n'en voyez presque jamais un . Cpp le fichier en inclut un autre .cpp fichier).

Comment ces externals sont résolus est un travail pour l'éditeur de liens. Lorsque votre compilateur traite main.cpp, il obtient les déclarations du code dans class.cpp en incluant class.h. Il suffit de savoir à quoi ressemblent ces fonctions ou ces variables (ce que vous donne une déclaration). Donc, il compile votre fichier main.cpp dans un fichier objet (appelez-le main.obj). De même, class.cpp est compilé dans un fichier class.obj. Pour produire l'exécutable final, un éditeur de liens est appelé pour lier ces deux fichiers objet. Pour toutes les variables ou fonctions externes non résolues, le compilateur placera un stub à l'endroit où l'accès se produit. L'éditeur de liens prend ensuite ce stub et cherche le code ou la variable dans un autre fichier objet répertorié. S'il le trouve, il combine le code des deux fichiers objet dans un fichier de sortie et remplace le stub par l'emplacement final de la fonction ou variable. De cette façon, votre code dans main.cpp peut appeler des fonctions et utiliser des variables dans class.cpp SI ET UNIQUEMENT SI ILS SONT DÉCLARÉS DANS class.h.

J'espère que cela a été utile.

275
Justin Summerlin

Le CLASS_H est un inclure garde ; cela évite d'inclure plusieurs fois le même fichier d'en-tête (via différentes routes) dans le même fichier CPP (ou, plus précisément, la même nité de traduction ), ce qui entraînerait des erreurs de définition multiple.

Les protections d'inclusion ne sont pas nécessaires dans les fichiers CPP car, par définition, le contenu du fichier CPP n'est lu qu'une seule fois.

Vous semblez avoir interprété les gardes d'inclusion comme ayant la même fonction que les instructions import dans d'autres langages (tels que Java); ce n'est pas le cas, cependant. Le #include lui-même est à peu près équivalent à import dans d’autres langues.

12
Martin B

Ce n'est pas le cas, du moins pendant la phase de compilation.

La traduction d'un programme c ++ du code source en code machine s'effectue en trois phases:

  1. prétraitement - Le préprocesseur analyse tout le code source des lignes commençant par # et exécute les directives. Dans votre cas, le contenu de votre fichier class.h Est inséré à la place de la ligne #include "class.h. Etant donné que vous pourriez être inclus dans votre fichier d'en-tête à plusieurs endroits, les clauses #ifndef Évitent les erreurs de déclaration en double, car la directive préprocesseur n'est pas définie uniquement lors de la première inclusion du fichier d'en-tête.
  2. Compilation - Le compilateur traduit maintenant tous les fichiers de code source prétraités en fichiers d'objet binaires.
  3. Liaison - L'éditeur de liens relie (d'où son nom) les fichiers d'objet. Une référence à votre classe ou à l'une de ses méthodes (qui doivent être déclarées dans class.h et définies dans class.cpp) est résolue en tant que décalage respectif dans l'un des fichiers objet. J'écris 'un de vos fichiers objet' car votre classe n'a pas besoin () dans un fichier nommé class.cpp, il se peut qu'elle soit dans une bibliothèque. qui est lié à votre projet.

En résumé, les déclarations peuvent être partagées via un fichier d'en-tête, tandis que le mappage des déclarations aux définitions est effectué par l'éditeur de liens.

5
sum1stolemyname

Cela est fait pour les fichiers d'en-tête afin que le contenu n'apparaisse qu'une fois dans chaque fichier source prétraité, même s'il est inclus plusieurs fois (généralement parce qu'il est inclus dans d'autres fichiers d'en-tête). La première fois qu'il est inclus, le symbole CLASS_H (connu sous le nom include guard) n’a pas encore été défini, de sorte que tout le contenu du fichier est inclus. Faire cela définit le symbole, donc s'il est inclus à nouveau, le contenu du fichier (à l'intérieur du #ifndef/#endif bloc) sont ignorés.

Il n'est pas nécessaire de faire cela pour le fichier source lui-même car (normalement), il n'est inclus dans aucun autre fichier.

Pour votre dernière question, class.h devrait contenir la définition de la classe et les déclarations de tous ses membres, des fonctions associées et de tout autre élément, afin que tout fichier l’incluant ait suffisamment d’informations pour utiliser la classe. Les implémentations des fonctions peuvent aller dans un fichier source séparé; vous avez seulement besoin des déclarations pour les appeler.

3
Mike Seymour

C'est la distinction entre déclaration et définition. Les fichiers d'en-tête incluent généralement uniquement la déclaration et le fichier source contient la définition.

Pour utiliser quelque chose, il vous suffit de connaître sa déclaration et non sa définition. Seul l'éditeur de liens doit connaître la définition.

C’est pourquoi vous allez inclure un fichier d’en-tête dans un ou plusieurs fichiers source, sans toutefois inclure un fichier source dans un autre.

Aussi tu veux dire #include et non importé.

3
Brian R. Bondy

main.cpp ne doit pas savoir ce qu'il y a dans class.cpp. Il suffit de connaître les déclarations des fonctions/classes qu'il va utiliser et ces déclarations sont dans class.h.

L'éditeur de liens relie les endroits où les fonctions/classes déclarées dans class.h sont utilisées et leurs implémentations dans class.cpp

2
Igor Oks

On s'attend généralement à ce que des modules de code tels que .cpp Les fichiers sont compilés une fois et liés à plusieurs projets afin d’éviter une compilation répétitive inutile de la logique. Par exemple, g++ -o class.cpp produirait class.o que vous pouvez ensuite lier à partir de plusieurs projets pour utiliser g++ main.cpp class.o.

Nous pourrions utiliser #include en tant que notre linker, comme vous semblez l’impliquer, mais ce serait idiot de savoir comment lier correctement en utilisant notre compilateur avec moins de frappes au clavier et moins de répétitions inutiles de la compilation, plutôt que notre code avec plus de frappes au clavier et des répétitions plus inutiles de compilation ...

Les fichiers d'en-tête doivent toujours être inclus dans chacun des projets, car ils fournissent l'interface pour chaque module. Sans ces en-têtes, le compilateur ne connaît aucun des symboles introduits par le .o des dossiers.

Il est important de réaliser que ce sont les fichiers d’en-tête qui introduisent les définitions des symboles pour ces modules; une fois que cela est réalisé, il est logique que de multiples inclusions puissent provoquer des redéfinitions de symboles (ce qui provoque des erreurs), nous utilisons donc des protecteurs pour empêcher de telles redéfinitions.

1
autistic

Les fichiers .cpp Ne sont pas inclus (utilisez #include) Dans d'autres fichiers. Par conséquent, ils n'ont pas besoin d'inclure la garde. Main.cpp Connaîtra les noms et les signatures de la classe que vous avez implémentée dans class.cpp Uniquement parce que vous avez spécifié tout cela dans class.h - c'est le but d'un fichier d'en-tête. (C’est à vous de vous assurer que class.h Décrit correctement le code que vous implémentez dans class.cpp.) Le code exécutable dans class.cpp Sera mis à la disposition du code exécutable dans main.cpp Grâce aux efforts de l'éditeur de liens.

1
Kate Gregory

c'est parce que Headerfiles définit ce que la classe contient (Membres, structures de données) et que les fichiers cpp l'implémentent.

Et bien sûr, la principale raison à cela est que vous pouvez inclure un fichier .h plusieurs fois dans d'autres fichiers .h, mais cela aboutirait à plusieurs définitions d'une classe, qui n'est pas valide.

0
Quonux