Pourquoi C ++ a-t-il des fichiers d'en-tête et des fichiers .cpp?
Pourquoi C ++ a-t-il des fichiers d'en-tête et des fichiers .cpp?
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" fera une classe (ou quoi que ce soit en cours d'implémentation), tandis que le fichier cpp définit "comment" il exécutera 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 également la quantité de recompilation nécessaire lorsqu'un élément de l'implémentation change.
Ce n'est pas parfait, et vous auriez généralement recours à des techniques comme l' idiome Pimpl 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 majeures:
Le premier est la compilation de fichiers texte "source" en fichiers "objets" 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 qu'il ne soit alimenté par une déclaration brute ou inclusion d'en-tête. Le fichier CPP est généralement compilé dans un fichier .OBJ ou .O "objet".
La seconde est la liaison entre 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 HPP 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, comme:
// A.CPP
void doSomething()
{
doSomethingElse(); // Defined in B.CPP
}
// B.CPP
void doSomethingElse()
{
// Etc.
}
Il ne sera pas compilé car A.CPP n'a aucun moyen de savoir que "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 ensuite la déclaration ...
Oui, il y a un problème. Les copies / pâtes sont dangereuses et difficiles à entretenir. Ce qui signifie que ce serait cool si nous avions un moyen de NE PAS copier / coller, et de 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
}
include
marche?L'inclusion d'un fichier analysera, puis copiera-collera 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 inclusion:
// B.CPP
void someFunction();
void someOtherFunction();
void doSomething()
{
// Etc.
}
Dans le cas actuel, cela n'est pas nécessaire, et B.HPP a la 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 en ligne), il pourrait ne pas y avoir de définition correspondante (par exemple, des énumérations, des 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 "de bon goût" qu'une source inclue par défaut son en-tête.
Le fichier d'en-tête est donc nécessaire, car le compilateur C ++ ne peut pas rechercher seul les déclarations de symboles, et donc, vous devez l'aider en incluant ces déclarations.
Un dernier mot: vous devez placer des protections d'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 pense que la principale raison 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_
ou encore plus simple
#pragma once
// The declarations in the B.hpp file
You still have to copy paste the signature from header file to cpp file, don't you?
Pas besoin. Tant que le CPP "inclut" le HPP, le précompilateur fera automatiquement le copier-coller du contenu du fichier HPP dans le fichier CPP. J'ai mis à jour la réponse pour clarifier cela.
While compiling A.cpp, compiler knows the types of arguments and return value of doSomethingElse from the call itself
. Non, non. Il ne connaît que les types fournis par l'utilisateur, qui, la moitié du temps, ne prendront même pas la peine de lire la valeur de retour. Ensuite, des conversions implicites se produisent. Et puis, quand vous avez le code:, foo(bar)
vous ne pouvez même pas être sûr qu'il foo
s'agit d'une fonction. Le compilateur doit donc avoir accès aux informations contenues dans les en-têtes pour décider si la source se compile correctement ou non ... Ensuite, une fois le code compilé, l'éditeur de liens reliera simplement les appels de fonctions.
Seems, they're just a pretty ugly arbitrary design.
: Si C ++ avait été créé en 2012, en effet. Mais rappelez-vous que C ++ a été construit sur C dans les années 1980, et à cette époque, les contraintes étaient assez différentes à cette époque (IIRC, il a été décidé à des fins d'adoption de conserver les mêmes linkers que C).
foo(bar)
une fonction - si elle est obtenue comme un pointeur? En fait, en parlant de mauvaise conception, je blâme C, pas C ++. Je n'aime vraiment pas certaines contraintes de C pur, comme avoir des fichiers d'en-tête ou avoir des fonctions renvoyant une et une seule valeur, tout en prenant plusieurs arguments en entrée (ne semble-t-il pas naturel que l'entrée et la sortie se comportent de la même manière ; pourquoi plusieurs arguments, mais une seule sortie?) :)
Why can't I be sure, that foo(bar) is a function
foo pourrait être un type, donc vous auriez un constructeur de classe appelé. In fact, speaking of bad design, I blame C, not C++
: Je peux blâmer C pour beaucoup de choses, mais avoir été conçu dans les années 70 n'en fera pas partie. Encore une fois, les contraintes de cette époque ... such as having header files or having functions return one and only one value
: Les tuples peuvent aider à atténuer cela, ainsi qu'à transmettre des arguments par référence. Maintenant, quelle serait la syntaxe pour récupérer plusieurs valeurs retournées, et cela vaudrait-il la peine de changer la langue?
Parce que C, à l'origine du concept, a 30 ans, et à l'époque, c'était le seul moyen viable de lier le code de plusieurs fichiers.
Aujourd'hui, c'est un hack horrible 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 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, qui est distinct du code lui-même. Cette description se trouve dans le fichier d'en-tête.
Parce que C ++ les a hérités de C. Malheureusement.
Parce que les personnes qui ont conçu le format de bibliothèque ne voulaient pas "gaspiller" de l'espace pour des informations rarement utilisées comme les macros de préprocesseur C et les déclarations de fonctions.
Puisque vous avez besoin de ces informations pour dire à votre compilateur "cette fonction est disponible plus tard lorsque l'éditeur de liens fait son travail", ils ont dû créer un deuxième fichier où ces informations partagées pourraient être stockées.
La plupart des langages après C / C ++ stockent ces informations dans la sortie (Java bytecode, 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).
C'est la manière de préprocesseur de déclarer des interfaces. Vous mettez l'interface (déclarations de méthode) dans le fichier d'en-tête et l'implémentation dans le cpp. Les applications utilisant votre bibliothèque doivent seulement connaître l'interface, à laquelle elles peuvent accéder via #include.
Souvent, vous voudrez avoir une définition d'une interface sans avoir à expédier le code entier. Par exemple, si vous avez une bibliothèque partagée, vous expédieriez un fichier d'en-tête qui définit toutes les fonctions et symboles utilisés dans la bibliothèque partagée. Sans fichiers d'en-tête, vous devez envoyer la source.
Dans un même projet, les fichiers d'en-tête sont utilisés, à mon humble avis, à au moins deux fins:
En réponse à la réponse de MadKeithV ,
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, ainsi que la quantité de recompilation nécessaire lorsque quelque chose dans l'implémentation change.
Une autre raison est qu'un en-tête donne un identifiant unique à chaque classe.
Donc, si nous avons quelque chose comme
class A {..};
class B : public A {...};
class C {
include A.cpp;
include B.cpp;
.....
};
Nous aurons des erreurs, lorsque nous essaierons de construire le projet, puisque A fait partie de B, avec des en-têtes nous éviterions ce genre de maux de tête ...