Pourquoi C++ a-t-il des fichiers d'en-tête et des fichiers .cpp?
Eh bien, la raison principale serait de séparer l'interface de l'implémentation. L'en-tête déclare "ce que" une classe (ou tout ce qui est implémenté) fera, alors que le fichier cpp définit "comment" ces fonctions seront exécutées.
Cela réduit les dépendances, de sorte que le code qui utilise l'en-tête n'a pas nécessairement besoin de connaître tous les détails de l'implémentation et les autres classes/en-têtes nécessaires uniquement pour cela. Cela réduira les temps de compilation et la quantité de recompilation nécessaire lorsqu'un élément de la mise en œuvre change.
Ce n'est pas parfait, et vous utiliserez généralement des techniques telles que le Pimpl Idiom pour séparer correctement l'interface et la mise en œuvre, mais c'est un bon début.
Une compilation en C++ se fait en 2 phases principales:
Le premier est la compilation de fichiers texte "source" en fichiers "objet" binaires: le fichier CPP est le fichier compilé et est compilé sans aucune connaissance des autres fichiers CPP (ni même des bibliothèques), à moins d'être alimenté par une déclaration brute ou inclusion d'en-tête. Le fichier CPP est généralement compilé dans un fichier "objet" .OBJ ou .O.
La seconde est la liaison de tous les fichiers "objets", et donc la création du fichier binaire final (une bibliothèque ou un exécutable).
Où se situe le PHP dans tout ce processus?
La compilation de chaque fichier CPP est indépendante de tous les autres fichiers CPP, ce qui signifie que si A.CPP a besoin d’un symbole défini dans B.CPP, tel que:
// A.CPP
void doSomething()
{
doSomethingElse(); // Defined in B.CPP
}
// B.CPP
void doSomethingElse()
{
// Etc.
}
Il ne compilera pas car A.CPP n'a aucun moyen de savoir que "quelque chose d'Else" existe ... Sauf s'il existe une déclaration dans A.CPP, telle que:
// A.CPP
void doSomethingElse() ; // From B.CPP
void doSomething()
{
doSomethingElse() ; // Defined in B.CPP
}
Ensuite, si vous avez C.CPP qui utilise le même symbole, vous copiez/collez la déclaration ...
Oui, il y a un problème. Les copies/pâtes sont dangereuses et difficiles à maintenir. Ce qui signifie que ce serait cool si nous avions un moyen de NE PAS copier/coller, et toujours déclarer le symbole ... Comment pouvons-nous le faire? Par l'inclusion de certains fichiers texte, qui sont généralement suffixés par .h, .hxx, .h ++ ou, mon préféré pour les fichiers C++, .hpp:
// B.HPP (here, we decided to declare every symbol defined in B.CPP)
void doSomethingElse() ;
// A.CPP
#include "B.HPP"
void doSomething()
{
doSomethingElse() ; // Defined in B.CPP
}
// B.CPP
#include "B.HPP"
void doSomethingElse()
{
// Etc.
}
// C.CPP
#include "B.HPP"
void doSomethingAgain()
{
doSomethingElse() ; // Defined in B.CPP
}
include
?L'inclusion d'un fichier va, en substance, analyser, puis copier-coller son contenu dans le fichier CPP.
Par exemple, dans le code suivant, avec l'en-tête A.HPP:
// A.HPP
void someFunction();
void someOtherFunction();
... la source B.CPP:
// B.CPP
#include "A.HPP"
void doSomething()
{
// Etc.
}
... deviendra après l'inclusion:
// B.CPP
void someFunction();
void someOtherFunction();
void doSomething()
{
// Etc.
}
Dans le cas présent, cela n'est pas nécessaire et B.HPP a la déclaration de fonction doSomethingElse
et B.CPP a la définition de fonction doSomethingElse
(qui est elle-même une déclaration). Mais dans un cas plus général, où B.HPP est utilisé pour les déclarations (et le code en ligne), il pourrait ne pas y avoir de définition correspondante (par exemple, enums, structures simples, etc.), de sorte que l'inclusion pourrait être nécessaire si B.CPP utilise ces déclarations de B.HPP. Globalement, il est "bon goût" pour une source d'inclure par défaut son en-tête.
Le fichier d'en-tête est donc nécessaire, car le compilateur C++ n'est pas en mesure de rechercher des déclarations de symbole seules. Vous devez donc l'aider en incluant ces déclarations.
Un dernier mot: Vous devriez mettre des gardes d’en-tête autour du contenu de vos fichiers HPP, pour vous assurer que les inclusions multiples ne cassent rien, mais dans l’ensemble, je crois que la raison principale de l’existence des fichiers HPP est expliquée ci-dessus.
#ifndef B_HPP_
#define B_HPP_
// The declarations in the B.hpp file
#endif // B_HPP_
Parce que C, à l'origine du concept, a 30 ans, il était à l'époque le seul moyen viable de lier du code à partir de plusieurs fichiers.
Aujourd'hui, c'est un pirate terrible qui détruit totalement le temps de compilation en C++, provoque d'innombrables dépendances inutiles (car les définitions de classe dans un fichier d'en-tête exposent trop d'informations sur l'implémentation), etc.
Parce qu'en C++, le code exécutable final ne contient aucune information de symbole, il s'agit d'un code machine plus ou moins pur.
Ainsi, vous avez besoin d’un moyen de décrire l’interface d’un morceau de code, distinct du code lui-même. Cette description est dans le fichier d'en-tête.
Parce que les concepteurs du format de bibliothèque ne voulaient pas "gaspiller" de l'espace pour des informations rarement utilisées, telles que les macros du préprocesseur C et les déclarations de fonction.
Puisque vous avez besoin de ces informations pour dire à votre compilateur "cette fonction est disponible plus tard lorsque l'éditeur de liens fonctionne", ils ont dû créer un deuxième fichier dans lequel ces informations partagées pourraient être stockées.
La plupart des langues après C/C++ stockent ces informations dans la sortie (bytecode Java, par exemple) ou n'utilisent pas du tout de format précompilé, sont toujours distribuées sous forme source et compilées à la volée (Python, Perl).
Parce que C++ les a hérités de C. Malheureusement.
C'est le moyen utilisé par le préprocesseur pour déclarer les interfaces. Vous mettez l'interface (déclarations de méthodes) dans le fichier d'en-tête et l'implémentation dans le cpp. Les applications utilisant votre bibliothèque doivent uniquement connaître l'interface à laquelle elles peuvent accéder via #include.
Vous voudrez souvent avoir une définition d'une interface sans avoir à expédier le code complet. Par exemple, si vous avez une bibliothèque partagée, vous devez lui envoyer un fichier d’en-tête qui définit toutes les fonctions et tous les symboles utilisés dans la bibliothèque partagée. Sans les fichiers d'en-tête, vous devez envoyer la source.
Dans un même projet, des fichiers d’en-tête sont utilisés, IMHO, pour au moins deux objectifs: