515 votes

Pourquoi avoir des fichiers d'en-tête et des fichiers .cpp en C++ ?

Pourquoi le C++ a-t-il des fichiers d'en-tête et des fichiers .cpp ?

636voto

paercebal Points 38526

Compilation C++

Une compilation en C++ se fait en deux grandes phases :

La première est la compilation des fichiers texte "source" en fichiers binaires "objet" :

Le fichier CPP est le fichier compilé et il est compilé sans aucune connaissance des autres fichiers CPP (ou même des bibliothèques), à moins qu'ils ne lui soient fournis par une déclaration brute ou une 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 (soit une bibliothèque, soit un exécutable).

Où se situe le HPP dans tout ce processus ?

Un pauvre fichier CPP solitaire...

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, comme :

// A.CPP
void doSomething()
{
   doSomethingElse(); // Defined in B.CPP
}

// B.CPP
void doSomethingElse()
{
   // Etc.
}

Il ne compilera pas parce que A.CPP n'a aucun moyen de savoir que "doSomethingElse" existe... A moins qu'il y ait une déclaration dans A.CPP, comme :

// 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 pouvez alors copier/coller la déclaration...

ALERTE AU COPIER/COLLER !

Oui, il y a un problème. Les copier/coller sont dangereux, et difficiles à maintenir. Ce qui veut dire que ce serait cool si on avait un moyen de ne PAS faire de copier/coller, tout en déclarant le symbole... Comment pouvons-nous le faire ? Par l'inclusion d'un fichier texte, qui est généralement suffixé 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
}

Comment include travail ?

L'inclusion d'un fichier va, en substance, analyser et 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.
}

Une petite chose - pourquoi inclure B.HPP dans B.CPP ?

Dans le cas présent, cela n'est pas nécessaire, et B.HPP dispose de l'expertise nécessaire. doSomethingElse et B.CPP a la déclaration de la fonction doSomethingElse la définition de la fonction (qui est, en soi, 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, structs simples, etc.), donc l'inclusion pourrait être nécessaire si B.CPP utilise ces déclarations de B.HPP. En somme, il est de "bon goût" pour une source d'inclure par défaut son en-tête.

Conclusion

Le fichier d'en-tête est donc nécessaire, car le compilateur C++ est incapable de rechercher les déclarations de symboles seules, et 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 être sûr 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.

217voto

Joris Timmermans Points 8075

La raison principale serait de séparer l'interface de l'implémentation. L'en-tête déclare "ce" qu'une classe (ou ce qui est implémenté) va faire, tandis que le fichier cpp définit "comment" il va réaliser ces fonctionnalités.

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 de toutes les autres classes/en-têtes nécessaires uniquement pour cela. Cela réduit les temps de compilation ainsi que la quantité de recompilation nécessaire lorsque quelque chose dans l'implémentation change.

Ce n'est pas parfait, et vous auriez généralement recours à des techniques comme le Idiom de Pimpl pour séparer correctement l'interface et l'implémentation, mais c'est un bon début.

99voto

jalf Points 142628

Parce que le langage C, qui est à l'origine de ce concept, a 30 ans et qu'à l'époque, c'était le seul moyen viable de relier entre eux des codes provenant de plusieurs fichiers.

Aujourd'hui, il s'agit d'un hack horrible qui détruit totalement le temps de compilation en C++, provoque d'innombrables dépendances inutiles (parce que les définitions de classe dans un fichier d'en-tête exposent trop d'informations sur l'implémentation), et ainsi de suite.

57voto

unwind Points 181987

Parce qu'en C++, le code exécutable final ne porte aucune information sur les symboles, il s'agit plus ou moins de code machine pur.

Ainsi, vous avez besoin d'un moyen de décrire l'interface d'un morceau de code, qui est séparé du code lui-même. Cette description se trouve dans le fichier d'en-tête.

20voto

andref Points 3297

Parce que le C++ les a hérités du C. Malheureusement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X