Le fait est qu'en C ++, c'est un peu plus compliqué que l'organisation en-tête / source C.
Que voit le compilateur?
Le compilateur voit un gros fichier source (.cpp) avec ses en-têtes correctement inclus. Le fichier source est l'unité de compilation qui sera compilée dans un fichier objet.
Alors, pourquoi les en-têtes sont-ils nécessaires?
Parce qu'une unité de compilation peut avoir besoin d'informations sur une implémentation dans une autre unité de compilation. On peut donc écrire par exemple l'implémentation d'une fonction dans une source, et écrire la déclaration de cette fonction dans une autre source ayant besoin de l'utiliser.
Dans ce cas, il existe deux copies des mêmes informations. Ce qui est mal ...
La solution est de partager quelques détails. Alors que l'implémentation doit rester dans la source, la déclaration de symboles partagés, comme les fonctions, ou la définition de structures, classes, énumérations, etc., pourrait devoir être partagée.
Les en-têtes sont utilisés pour mettre ces détails partagés.
Déplacez vers l'en-tête les déclarations de ce qui doit être partagé entre plusieurs sources
Rien de plus?
En C ++, il y a d'autres choses qui pourraient être placées dans l'en-tête car elles doivent aussi être partagées:
- code en ligne
- modèles
- constantes (généralement celles que vous souhaitez utiliser à l'intérieur des commutateurs ...)
Déplacer vers l'en-tête TOUT ce qui doit être partagé, y compris les implémentations partagées
Cela signifie-t-il alors qu'il pourrait y avoir des sources à l'intérieur des en-têtes?
Oui. En fait, il y a beaucoup de choses différentes qui pourraient être à l'intérieur d'un "en-tête" (c'est-à-dire partagées entre les sources).
- Déclarations prospectives
- déclarations / définition de fonctions / structs / classes / templates
- implémentation de code en ligne et basé sur des modèles
Cela devient compliqué, et dans certains cas (dépendances circulaires entre symboles), impossible de le conserver dans un en-tête.
Les en-têtes peuvent être divisés en trois parties
Cela signifie que, dans un cas extrême, vous pourriez avoir:
- un en-tête de déclaration avant
- un en-tête de déclaration / définition
- un en-tête d'implémentation
- une source d'implémentation
Imaginons que nous ayons un MyObject basé sur un modèle. Nous pourrions avoir:
// - - - - MyObject_forward.hpp - - - -
// This header is included by the code which need to know MyObject
// does exist, but nothing more.
template<typename T>
class MyObject ;
.
// - - - - MyObject_declaration.hpp - - - -
// This header is included by the code which need to know how
// MyObject is defined, but nothing more.
#include <MyObject_forward.hpp>
template<typename T>
class MyObject
{
public :
MyObject() ;
// Etc.
} ;
void doSomething() ;
.
// - - - - MyObject_implementation.hpp - - - -
// This header is included by the code which need to see
// the implementation of the methods/functions of MyObject,
// but nothing more.
#include <MyObject_declaration.hpp>
template<typename T>
MyObject<T>::MyObject()
{
doSomething() ;
}
// etc.
.
// - - - - MyObject_source.cpp - - - -
// This source will have implementation that does not need to
// be shared, which, for templated code, usually means nothing...
#include <MyObject_implementation.hpp>
void doSomething()
{
// etc.
} ;
// etc.
Hou la la!
Dans la «vraie vie», c'est généralement moins compliqué. La plupart du code n'aura qu'un simple en-tête / organisation source, avec du code incorporé dans la source.
Mais dans d'autres cas (objets basés sur des modèles se connaissant les uns les autres), je devais avoir pour chaque objet des en-têtes de déclaration et d'implémentation séparés, avec une source vide comprenant ces en-têtes juste pour m'aider à voir certaines erreurs de compilation.
Une autre raison de décomposer les en-têtes en en-têtes séparés pourrait être d'accélérer la compilation, de limiter la quantité de symboles analysés au strict nécessaire et d'éviter la recompilation inutile d'une source qui ne se soucie que de la déclaration directe lorsqu'une implémentation de méthode en ligne a changé.
Conclusion
Vous devez rendre l'organisation de votre code aussi simple que possible et aussi modulaire que possible. Mettez autant que possible dans le fichier source. N'exposez dans les en-têtes que ce qui doit être partagé.
Mais le jour où vous aurez des dépendances circulaires entre des objets modèles, ne soyez pas surpris si l'organisation de votre code devient un peu plus "intéressante" que l'organisation simple en-tête / source ...
^ _ ^