Question Pourquoi avoir des fichiers d'en-tête et des fichiers .cpp? [fermé]


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


412


origine


Réponses:


Eh bien, la principale raison 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, tandis que le fichier cpp définit "comment" il va exécuter 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 toutes les autres classes / en-têtes nécessaires uniquement pour cela. Cela réduira les temps de compilation et aussi la quantité de recompilation nécessaire lorsque quelque chose dans l'implémentation change.

Ce n'est pas parfait, et vous auriez habituellement recours à des techniques comme Pimpl Idiom séparer correctement l'interface et la mise en œuvre, mais c'est un bon début.


181



Compilation C ++

Une compilation en C ++ se fait en 2 phases majeures:

  1. 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 (ou même des bibliothèques), à moins d’être transmis par déclaration brute ou inclusion d'en-tête. Le fichier CPP est généralement compilé dans un fichier .OBJ ou un objet ".O".

  2. La seconde est l'enchaînement de tous les fichiers "objet", et donc la création du fichier binaire final (soit une bibliothèque soit un exécutable).

Où se situe le PHP 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 car A.CPP n'a aucun moyen de savoir "doSomethingElse" existe ... Sauf s'il y a 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 copiez / collez la déclaration ...

COPIE / ALERTE DE PÂTE!

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 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 ça include travail?

Y compris un fichier, par essence, 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.
}

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

Dans le cas présent, cela n’est pas nécessaire et B.HPP a le doSomethingElse déclaration de fonction, et B.CPP a la doSomethingElse définition de 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 inline), il ne pourrait y avoir de définition correspondante (par exemple, enums, structures simples, etc.), donc l'inclusion pourrait être nécessaire si B.CPP utilise ces déclarations de B.HPP. Dans l'ensemble, il est "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 uniquement les déclarations de symboles et vous devez donc l'aider en incluant ces déclarations.

Un dernier mot: Vous devriez mettre des gardes en-tête autour du contenu de vos fichiers HPP, pour être sûr que plusieurs inclusions ne cassent rien, mais dans l'ensemble, je crois que la raison principale de l'existence des fichiers HPP est expliquée plus haut.

#ifndef B_HPP_
#define B_HPP_

// The declarations in the B.hpp file

#endif // B_HPP_

533



Parce que C, dont le concept est originaire, a 30 ans, et à l'époque, c'était le seul moyen viable de relier le code de plusieurs fichiers.

Aujourd'hui, c'est un hack effrayant 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'entête exposent trop d'informations sur l'implémentation), et ainsi de suite.


76



Parce qu'en C ++, le code exécutable final ne porte aucune information de symbole, c'est du 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.


51



Parce que les personnes qui ont conçu le format de bibliothèque ne voulaient pas gaspiller d’espace pour des informations rarement utilisées telles que les macros préprocesseurs C et les déclarations de fonctions.

Puisque vous avez besoin de cette information pour dire à votre compilateur "cette fonction est disponible plus tard quand l'éditeur de liens fait son travail", ils ont dû trouver un second fichier où ces informations partagées pourraient être stockées.

La plupart des langages après C / C ++ stockent ces informations dans la sortie (bytecode Java, par exemple) ou n'utilisent pas du tout un format précompilé, sont toujours distribués sous forme source et compilent à la volée (Python, Perl).


11



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


10



C'est la manière du préprocesseur de déclarer les interfaces. Vous placez l'interface (déclarations de méthode) dans le fichier d'en-tête et l'implémentation dans le fichier cpp. Les applications utilisant votre bibliothèque n'ont besoin que de connaître l'interface, à laquelle elles peuvent accéder via #include.


4