Nettoyer vos déclarations #include?

Comment maintenez-vous les instructions #include dans votre projet C ou C ++? Il semble presque inévitable qu’éventuellement, l’ensemble des instructions include d’un fichier soit insuffisant (mais qu’il fonctionne à cause de l’état actuel du projet) ou qu’il inclut des éléments inutiles.

Avez-vous créé des outils pour détecter ou résoudre les problèmes? Aucune suggestion?

J’ai envisagé d’écrire quelque chose qui comstack chaque fichier non-en-tête individuellement plusieurs fois, en supprimant à chaque fois une instruction #include. Continuez ainsi jusqu’à obtenir un nombre minimal d’inclusions.

Pour vérifier que les fichiers d’en-tête incluent tout ce dont ils ont besoin, je voudrais créer un fichier source qui n’inclut qu’un fichier d’en-tête et tente de le comstackr. Si la compilation échoue, le fichier d’en-tête manque d’inclusion.

Avant de créer quelque chose cependant, je pensais que je devrais demander ici. Cela semble être un problème quelque peu universel.

Pour vérifier que les fichiers d’en-tête incluent tout ce dont ils ont besoin, je voudrais créer un fichier source qui n’inclut qu’un fichier d’en-tête et tente de le comstackr. Si la compilation échoue, le fichier d’en-tête manque d’inclusion.

Vous obtenez le même effet en faisant la règle suivante: le premier fichier d’en-tête que foo .c ou foo .cpp doit inclure doit être le foo .h ainsi nommé. Cela garantit que foo .h inclut tout ce dont il a besoin pour comstackr.

En outre, l’ouvrage de Lakos intitulé Large-Scale C ++ Software Design (par exemple) répertorie de nombreuses techniques permettant de déplacer les détails de la mise en œuvre hors d’un en-tête et dans le fichier CPP correspondant. Si vous prenez cela à l’extrême, en utilisant des techniques telles que Cheshire Cat (qui cache tous les détails de la mise en œuvre) et Factory (qui cache l’existence de sous-classes), de nombreux en-têtes pourront alors se tenir seuls sans inclure d’autres en-têtes et se contenter de transmettre la déclaration à des types opaques à la place … sauf peut-être pour les classes de modèle.

En fin de compte, chaque fichier d’en-tête devra peut-être inclure:

  • Aucun fichier d’en-tête pour les types qui sont des membres de données (à la place, les membres de données sont définis / cachés dans le fichier CPP en utilisant la technique “cheshire cat”, aussi appelée “pimpl”)

  • Pas de fichiers d’en-tête pour les types qui sont des parameters ou qui retournent des types depuis des méthodes (il s’agit plutôt de types prédéfinis comme int ; ou, s’ils sont définis par l’utilisateur, ils sont des références, auquel cas un type opaque déclaré Une déclaration comme la simple class Foo; au lieu de #include "foo.h" dans le fichier d’en-tête est suffisante).

Ce dont vous avez besoin, c’est le fichier d’en-tête pour:

  • La superclasse, s’il s’agit d’une sous-classe

  • Peut-être tous les types basés sur des modèles utilisés comme parameters de méthode et / ou types de retour: apparemment, vous êtes censé être capable de déclarer des classes de modèles aussi, mais certaines implémentations de compilateur peuvent poser problème (bien que vous puissiez également encapsuler des modèles) par exemple, List tant que détails de mise en œuvre d’un type défini par l’ ListX par exemple, ListX ).

En pratique, je pourrais créer un “standard.h” qui inclut tous les fichiers système (par exemple, les en-têtes STL, les types spécifiques à O / S et / ou tous les #define s, etc.) utilisés par tous les fichiers d’en-tête du répertoire. projet, et incluez-le comme premier en-tête dans chaque fichier d’en-tête d’application (et indiquez au compilateur de traiter ce “standard.h” comme “fichier d’en-tête précompilé”).


 //contents of foo.h #ifndef INC_FOO_H //or #pragma once #define INC_FOO_H #include "standard.h" class Foo { public: //methods ... Foo-specific methods here ... private: //data struct Impl; Impl* m_impl; }; #endif//INC_FOO_H 

 //contents of foo.cpp #include "foo.h" #include "bar.h" Foo::Foo() { m_impl = new Impl(); } struct Foo::Impl { Bar m_bar; ... etc ... }; ... etc ... 

J’ai l’habitude de commander mes inclus de niveau d’abstraction élevé à faible niveau d’abstraction. Cela nécessite que les en-têtes soient autonomes et que les dépendances cachées se révèlent rapidement comme des erreurs de compilation.

Par exemple, une classe ‘Tesortings’ a un fichier Tesortings.h et Tesortings.cpp. L’ordre d’inclusion pour Tesortings.cpp serait

 #include "Tesortings.h" // corresponding header first #include "Block.h" // ..then application level includes #include "Utils/Grid.h" // ..then library dependencies #include  // ..then stl #include  // ..then system includes 

Et maintenant, je réalise que cela ne répond pas vraiment à votre question, car ce système n’aide pas vraiment à nettoyer les éléments inutiles. Et bien..

La détection d’inclusions superflues a déjà été abordée dans cette question .

Je ne connais aucun outil pour aider à détecter les inclusions insuffisantes, mais qui arrivent au travail, mais de bonnes conventions de codage peuvent aider ici. Par exemple, le Guide de style de Google C ++ exige ce qui suit, dans le but de réduire les dépendances masquées:

Dans dir/foo.cc , dont le but principal est d’implémenter ou de tester les éléments dans dir2/foo2.h , dir2/foo2.h votre inclut comme suit:

  1. dir2/foo2.h (emplacement préféré – voir détails ci-dessous).
  2. C fichiers système.
  3. Fichiers système C ++.
  4. Fichiers .h des autres bibliothèques.
  5. Les fichiers .h de votre projet.

En fonction de la taille de votre projet, il peut être utile de consulter les graphiques d’inclusion créés par Doxygen (avec l’option INCLUDE_GRAPH activée).

Un gros problème avec la technique de suppression d’un en-tête et de recompilation est qu’elle peut conduire à un code toujours en cours de compilation, mais erroné ou inefficace.

  1. Spécialisation de modèle: si vous avez une spécialisation de modèle pour un type spécifique qui se trouve dans un en-tête et un modèle plus général dans un autre, la suppression de la spécialisation peut laisser le code dans un état compilable, mais avec des résultats non souhaités.

  2. Résolution de surcharge: Un problème similaire – si vous avez deux surcharges d’une fonction dans des en-têtes différents, mais que cela prend des types quelque peu compatibles, vous pouvez supprimer la version qui convient le mieux dans un cas, tout en conservant le code compilé. Ceci est probablement moins probable que la version de spécialisation de modèle, mais c’est possible.

J’ai envisagé d’écrire quelque chose qui comstack chaque fichier non-en-tête individuellement plusieurs fois, en supprimant à chaque fois une instruction #include. Continuez ainsi jusqu’à obtenir un nombre minimal d’inclusions.

Je pense que cela est erroné et que cela conduira à inclure des ensembles “insuffisants mais qui se trouve au travail”.

Supposons que votre fichier source utilise numeric_limits , mais inclut également un fichier d’en-tête qui, pour des raisons qui lui sont propres, inclut . Cela ne signifie pas que votre fichier source ne doit pas inclure . Cet autre fichier d’en-tête n’est probablement pas documenté pour définir tout ce qui est défini dans , il se trouve qu’il en est ainsi. Un jour, cela pourrait s’arrêter: peut-être n’utilise-t-il qu’une seule valeur comme paramètre par défaut d’une fonction et peut-être que cette valeur par défaut change de std::numeric_limits::min() à 0. Et maintenant, votre fichier source ne fonctionne plus. comstackr plus, et le mainteneur de ce fichier d’en-tête ne savait même pas que votre fichier existait jusqu’à ce qu’il se casse.

À moins que vous n’ayez des problèmes de compilation invalidants dès maintenant, je pense que la meilleure façon de supprimer les fichiers redondants est tout simplement de prendre l’habitude de parcourir la liste chaque fois que vous touchez un fichier à entretenir. Si vous constatez que vous avez des dizaines d’inclusions et que, après avoir examiné le fichier, vous ne savez toujours pas à quoi chacune est destinée, envisagez de la décomposer en fichiers plus petits.

Si vous utilisez le compilateur Visual Studio, vous pouvez essayer l’option de compilateur / showIncludes, puis parsingr ce qu’il émet dans stderr. MSDN: “Le compilateur génère une liste des fichiers d’inclusion. Les fichiers d’inclusion nesteds sont également affichés (les fichiers inclus à partir des fichiers que vous avez inclus).”

Ouaip. Nous avons notre propre pré-processeur qui nous donne access à notre propre langage macro. Il vérifie également que les fichiers d’en-tête ne sont inclus qu’une fois. Créer un pré-processeur simple vérifiant plusieurs inclusions devrait être assez facile.

En ce qui concerne les outils, j’ai utilisé Imagix (c’était il y a environ 6 ans) sur Windows pour identifier les éléments inutiles et ceux qui sont nécessaires mais qui sont indirectement inclus dans un autre élément.

Jetez un coup d’œil au projet cppclean . Bien qu’ils n’aient pas encore implémenté cette fonctionnalité, mais il est prévu de le faire.

Sur le site du projet:

CppClean tente de trouver des problèmes dans les sources C ++ qui ralentissent le développement, en particulier dans les bases de code volumineuses. C’est semblable à la charpie; Cependant, CppClean se concentre sur la recherche de problèmes globaux inter-modules plutôt que de problèmes locaux similaires à ceux d’autres outils d’parsing statique.

L’objective est de détecter les problèmes qui ralentissent le développement dans les bases de code volumineuses modifiées au fil du temps, laissant le code inutilisé. Ce code peut prendre de nombreuses formes, allant de fonctions, méthodes, membres de données, types, etc. inutilisés aux directives #include non nécessaires. Des #includes inutiles peuvent causer des compilations supplémentaires considérables, ce qui augmente le cycle édition-compilation-exécution.

Et particulièrement sur la fonctionnalité #include:

  • (prévu) Trouver les fichiers d’en-tête inutiles #included
    • Aucune référence directe à quoi que ce soit dans l’en-tête
    • L’en-tête est inutile si les classes ont été déclarées à la place
  • (prévus) Fichiers sources qui référencent les en-têtes non # inclus, c’est-à-dire les fichiers qui reposent sur un transitif #include provenant d’un autre en-tête

Ici vous pouvez trouver un miroir chez BitBucket.

Si vous codez dans Eclipse avec CDT, vous pouvez utiliser la commande Organiser les inclus. Appuyez simplement sur Ctrl + Maj + O pour append les éléments nécessaires à la suppression et supprimer ceux qui ne sont plus nécessaires.

Je crée généralement un fichier source (main.c, par exemple) et un fichier d’en-tête pour ce fichier source (main.h). Dans le fichier source, je mets toutes les principales sortes de “fonctions d’interface” que j’utilise dans ce fichier (dans main, ce serait main() ), puis toutes les fonctions que j’obtiens après avoir refactorisé ces fonctions (détails d’implémentation), allez en bas. Dans le fichier d’en-tête, je déclare certaines fonctions extern définies dans d’autres fichiers source, mais utilisées dans le fichier source utilisant cet en-tête. Ensuite, je déclare les structures ou autres types de données que j’utilise dans ce fichier source.

Ensuite, je viens de comstackr et de les lier tous ensemble. Il rest beau et propre. Une section include typique … dans mon projet actuel ressemble à ceci

 #include #include #include #include"interface.h" #include"thissourcefile.h" //function prototypes //source 

il y a un en-tête d’ interface qui garde la trace des structures de données que j’utilise dans toutes les formes du projet, puis de thissourcefile.h qui fait exactement ce que je viens d’expliquer (déclare externs , etc.)

De plus, je ne définis jamais rien dans mes en-têtes, je n’y mets que des déclarations. De cette façon, ils peuvent être inclus par différents fichiers source et toujours liés avec succès. Les prototypes de fonctions (externes, statiques ou autres) et les déclarations sont placés dans l’en-tête. Ils peuvent donc être utilisés très souvent. Les définitions sont dans le source, car elles ne doivent figurer qu’à un seul endroit.

Ce serait évidemment différent si vous créiez une bibliothèque ou quelque chose comme ça. Mais juste pour la liaison de projet interne, je trouve que ça garde tout bien et propre. De plus, si vous écrivez un makefile (ou utilisez simplement un IDE), alors comstackr est vraiment simple et efficace.