S'assurer que les en-têtes sont explicitement inclus dans le fichier CPP

9

Je pense que c'est généralement une bonne pratique d'en #include-tête pour tous les types utilisés dans un fichier CPP, indépendamment de ce qui est déjà inclus via le fichier HPP. Ainsi, je pourrais #include <string>à la fois dans mon HPP et mon CPP, par exemple, même si je pouvais encore compiler si je le sautais dans le CPP. De cette façon, je n'ai pas à me soucier de savoir si mon HPP a utilisé une déclaration à terme ou non.

Existe-t-il des outils pouvant appliquer ce #includestyle de codage? Dois- je appliquer ce style de codage?

Étant donné que le préprocesseur / compilateur ne se soucie pas de savoir si cela #includeprovient du HPP ou du CPP, je ne reçois aucun retour si j'oublie de suivre ce style.

M. Dudley
la source

Réponses:

7

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::stringou std::vector<SomeType>. Vous devez #includeces 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 barun Foomembre 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.oavec un outil tel que nm, il apparaîtra que les fonctions dans foo.ccappellent toutes sortes de choses pour lesquelles vous n'avez pas fait le bon choix #include. Devriez-vous donc ajouter des #includedirectives 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.

David Hammen
la source
2

Dois-je appliquer ce style de codage?

Probablement pas. Ma règle est la suivante: l'inclusion du fichier d'en-tête ne peut pas dépendre de l'ordre.

Vous pouvez vérifier cela assez facilement avec la règle simple que le premier fichier inclus par xc est xh

kevin cline
la source
1
Pouvez-vous élaborer sur ce sujet? Je ne vois pas comment cela pourrait vraiment vérifier l'indépendance de l'ordre.
detly
1
il ne garantit pas vraiment l'indépendance de la commande - il garantit simplement que #include "x.h"cela fonctionnera sans nécessiter de précédent #include. C'est assez bien si vous n'abusez pas #define.
Kevin Cline
1
Oh je vois. Encore une bonne idée alors.
detly
2

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".

Captain Obvlious
la source
1

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.

Bart van Ingen Schenau
la source
1

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 oncecô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. #ifdefchaîne inclut pour de meilleurs résultats avec cette méthode de test.

Ericson2314
la source
1

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.

Gvozden
la source
Pouvez-vous expliquer comment cela "raccourcira considérablement le temps de compilation"?
Mawg dit de réintégrer Monica
1
Cela garantit que chaque unité de compilation (fichier CPP) extrait uniquement le minimum de fichiers d'inclusion au moment de la compilation. Sinon, si vous placez des inclusions dans des fichiers H, de fausses chaînes de dépendance se forment rapidement et vous finissez par extraire toutes les inclusions à chaque compilation.
Gvozden
Avec les gardes d'inclusion, je pourrais peut-être poser une question "significativement", mais je suppose que l'accès au disque (afin de découvrir ces jauges incluses) est "lent", donc je vais céder le point. Merci d'avoir clarifié
Mawg dit de réintégrer Monica
0

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.

Dima
la source
3
Si vous laissez des inclus dans les en-têtes, alors inclure l'ordre commence à avoir de l'importance ... et je veux certainement éviter cela.
M. Dudley
1
-1: crée un cauchemar de dépendance. Une amélioration de xh pourrait nécessiter une modification dans CHAQUE fichier .cpp qui inclut xh
kevin cline
1
@ M.Dudley, Comme je l'ai dit, il y a des avantages et des inconvénients.
Dima