Il s'agit de l'un de ces types de normes de codage «devrait» plutôt que «doit». La raison en est que vous auriez à peu près à écrire un analyseur C ++ pour l'appliquer.
Une règle très courante pour les fichiers d'en-tête est qu'ils doivent être autonomes. Un fichier d'en-tête ne doit pas exiger que certains autres fichiers d'en-tête soient inclus avant d'inclure l'en-tête en question. Il s'agit d'une exigence testable. Étant donné un en-tête aléatoire foo.hh
, les éléments suivants doivent être compilés et exécutés:
#include "foo.hh"
int main () {
return 0;
}
Cette règle a des conséquences en ce qui concerne l'utilisation d'autres classes dans certains en-têtes. Parfois, ces conséquences peuvent être évitées en déclarant ces autres classes. Ce n'est pas possible avec de nombreuses classes de bibliothèque standard. Il n'y a aucun moyen de déclarer une instanciation de modèle comme std::string
ou std::vector<SomeType>
. Vous devez #include
ces en-têtes STL dans l'en-tête même si la seule utilisation du type est comme argument d'une fonction.
Un autre problème concerne les éléments que vous faites glisser accidentellement. Exemple: tenez compte des éléments suivants:
fichier foo.cc:
#include "foo.hh"
#include "bar.hh"
void Foo::Foo () : bar() { /* body elided */ }
void Foo::do_something (int item) {
...
bar.add_item (item);
...
}
Voici bar
un Foo
membre de données de classe qui est de type Bar
. Vous avez fait la bonne chose ici et vous avez inclus #include bar.hh même si cela aurait dû être inclus dans l'en-tête qui définit la classe Foo
. Cependant, vous n'avez pas inclus les éléments utilisés par Bar::Bar()
et Bar::add_item(int)
. Il existe de nombreux cas où ces appels peuvent entraîner des références externes supplémentaires.
Si vous analysez foo.o
avec un outil tel que nm
, il apparaîtra que les fonctions dans foo.cc
appellent toutes sortes de choses pour lesquelles vous n'avez pas fait le bon choix #include
. Devriez-vous donc ajouter des #include
directives pour ces références externes accessoires foo.cc
? La réponse n'est absolument pas. Le problème est qu'il est très difficile de distinguer les fonctions appelées accessoirement de celles appelées directement.
#include "x.h"
cela fonctionnera sans nécessiter de précédent#include
. C'est assez bien si vous n'abusez pas#define
.Si vous devez appliquer une règle selon laquelle des fichiers d'en-tête particuliers doivent être autonomes, vous pouvez utiliser les outils dont vous disposez déjà. Créez un makefile de base qui compile chaque fichier d'en-tête individuellement mais ne génère pas de fichier objet. Vous pourrez spécifier dans quel mode compiler le fichier d'en-tête (mode C ou C ++) et vérifier qu'il peut être autonome. Vous pouvez faire l'hypothèse raisonnable que la sortie ne contient aucun faux positif, toutes les dépendances requises sont déclarées et la sortie est précise.
Si vous utilisez un IDE, vous pourrez toujours le faire sans makefile (selon votre IDE). Créez simplement un projet supplémentaire, ajoutez les fichiers d'en-tête que vous souhaitez vérifier et modifiez les paramètres pour le compiler en tant que fichier C ou C ++. Dans MSVC par exemple, vous modifieriez le paramètre "Type d'élément" dans "Propriétés de configuration -> Général".
la source
Je ne pense pas qu'un tel outil existe, mais je serais heureux si une autre réponse me démentait.
Le problème avec l'écriture d'un tel outil est qu'il rapporte très facilement un faux résultat, donc j'estime que l'avantage net d'un tel outil est proche de zéro.
La seule façon dont un tel outil pourrait fonctionner est de réinitialiser sa table de symboles au contenu du fichier d'en-tête qu'il a traité, mais vous rencontrez ensuite le problème que les en-têtes qui forment l'API externe d'une bibliothèque délèguent les déclarations réelles à en-têtes internes.
Par exemple,
<string>
dans l'implémentation de GCC libc ++ ne déclare rien, mais il inclut juste un tas d'en-têtes internes qui contiennent les déclarations réelles. Si l'outil réinitialise sa table de symboles juste à ce qui a été déclaré par<string>
lui-même, ce ne serait rien.L'outil pourrait faire la différence entre
#include ""
et#include <>
, mais cela ne vous aide pas si une bibliothèque externe utilise#include ""
pour inclure ses en-têtes internes dans l'API.la source
n'y parvient pas
#Pragma once
? Vous pouvez inclure quelque chose autant de fois que vous le souhaitez, soit directement ou via des inclus chaînés, et tant qu'il y a un à#Pragma once
côté de chacun d'eux, l'en-tête n'est inclus qu'une seule fois.En ce qui concerne son application, vous pouvez peut-être créer un système de construction qui inclut uniquement chaque en-tête avec une fonction principale factice, juste pour vous assurer qu'il compile.
#ifdef
chaîne inclut pour de meilleurs résultats avec cette méthode de test.la source
Incluez toujours les fichiers d'en-tête dans le fichier CPP. Cela raccourcit non seulement considérablement le temps de compilation, mais vous évite également beaucoup de problèmes si vous décidez d'utiliser des en-têtes précompilés. D'après mon expérience, même la peine de faire des déclarations avancées vaut la peine d'être pratiquée. Ne brisez la règle que lorsque cela est nécessaire.
la source
Je dirais qu'il y a à la fois des avantages et des inconvénients à cette convention. D'une part, il est agréable de savoir exactement ce que contient votre fichier .cpp. D'un autre côté, la liste des inclus peut facilement atteindre une taille ridicule.
Une façon d'encourager cette convention n'est pas d'inclure quoi que ce soit dans vos propres en-têtes, mais uniquement dans les fichiers .cpp. Ensuite, tout fichier .cpp utilisant votre en-tête ne sera pas compilé sauf si vous incluez explicitement tous les autres en-têtes dont il dépend.
Un compromis raisonnable est probablement de mise ici. Par exemple, vous pouvez décider qu'il est correct d'inclure des en-têtes de bibliothèque standard dans vos propres en-têtes, mais pas plus.
la source