Nettoyer vos déclarations # include?
comment maintenez-vous les déclarations #include dans votre projet C ou C++? Il semble presque inévitable que l'ensemble des déclarations include dans un fichier soit insuffisant (mais fonctionne en raison de l'état actuel du projet) ou inclut des choses qui ne sont plus nécessaires.
avez-vous créé des outils pour repérer ou corriger des problèmes? Toutes les suggestions?
j'ai pensé à écrire quelque chose qui compile chaque le fichier non-en-tête individuellement plusieurs fois, à chaque fois la suppression d'un énoncé #include. Continuez de le faire jusqu'à ce qu'un ensemble minimal d'inclusions soit atteint.
pour vérifier que les fichiers d'en-tête incluent tout ce dont ils ont besoin, je créerais un fichier source qui ne fait qu'inclure un fichier d'en-tête et essayer de le compiler. Si la compilation échoue, alors le fichier d'en-tête lui-même manque une include.
avant de créer quelque chose cependant, j'ai pensé que je devrais demander ici. Cela semble être un problème assez universel.
12 réponses
pour vérifier que les fichiers d'en-tête incluent tout ce dont ils ont besoin, je créerais un fichier source qui ne fait qu'inclure un fichier d'en-tête et essayer de le compiler. Si la compilation échoue, alors le fichier d'en-tête lui-même manque une include.
, Vous obtenez le même effet en faisant la règle suivante: le premier fichier d'en-tête qui foo .c ou foo .rpc doit inclure devrait être le foo .h. Cela garantit que foo .h inclut tout ce dont il a besoin pour compiler.
de plus, le livre de Lakos Large-Scale C++ Software Design (par exemple) énumère de nombreuses techniques pour déplacer des détails de mise en œuvre hors d'un en-tête et dans le fichier CPP correspondant. Si vous prenez cela à son extrême, en utilisant des techniques comme Cheshire Cat (qui cache toute la mise en œuvre détails) et de L'usine (qui dissimule l'existence de sous-classes), puis de nombreux en-têtes seraient en mesure de se tenir seul sans inclure d'autres en-têtes, et à la place de faire avec la déclaration juste de renvoi à des types opaques à la place ... sauf peut-être pour les classes de modèle.
à la fin, chaque fichier d'en-tête pourrait avoir besoin d'inclure:
-
Pas de fichiers d'en-tête pour les types de données des membres (au lieu de cela, les membres de données sont définis/caché dans le RPC file using the" cheshire cat " A. K. A. technique "pimpl")
-
pas de fichiers d'en-tête pour les types qui sont des paramètres ou des types de retour de méthodes (au lieu de cela, ce sont des types prédéfinis comme
int
; ou, si ce sont des types définis par l'utilisateur, alors ce sont des références dans ce cas, une déclaration de type Avancée et opaque comme simplementclass Foo;
au lieu de#include "foo.h"
dans le fichier d'en-tête est suffisante).
ce dont vous avez besoin alors est le fichier d'en-tête pour:
-
la superclasse, s'il s'agit d'une sous-classe
-
peut-être n'importe quels types templated qui sont utilisés comme paramètres de méthode et/ou types de retour: apparemment, vous êtes censés être en mesure de transmettre-déclarer des classes de template aussi, mais certaines implémentations de compilateur peuvent avoir un problème avec cela (bien que vous puissiez aussi encapsuler n'importe quels templates par exemple
List<X>
comme détails de mise en œuvre d'un type défini par l'utilisateur, par exempleListX
).
dans la pratique, je pourrais faire une" norme.h " qui inclut tous les fichiers système (par exemple, les en-têtes STL, les types O/S spécifiques et/ou les #define
s, etc.) qui sont utilisés par n'importe quel/tous les fichiers d'en-tête dans le projet, et inclure que comme premier en-tête dans chaque fichier d'en-tête d'application (et dire au compilateur de traiter ce "standard.h "comme le' 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 comprend de haut niveau d'abstraction de bas niveau d'abstraction. Cela nécessite que les en-têtes soient auto-suffisants et les dépendances cachées sont rapidement révélées comme des erreurs de compilation.
par exemple, une classe "Tetris" a un Tetris.h et Tetris.fichier cpp. La commande include pour Tetris.cpp serait
#include "Tetris.h" // corresponding header first
#include "Block.h" // ..then application level includes
#include "Utils/Grid.h" // ..then library dependencies
#include <vector> // ..then stl
#include <windows.h> // ..then system includes
et maintenant je me rends compte que cela ne répond pas vraiment à votre question puisque ce système n'est pas vraiment aider à nettoyer inutiles. Ah bien..
détection des inclusions superflues a déjà été discuté dans cette question .
Je ne suis pas au courant d'aucun outil pour aider à détecter insuffisant-mais-arrive-à-travail inclut, mais de bonnes conventions de codage peuvent aider ici. Par exemple, le Guide de style C++ de Google prescrit ce qui suit, dans le but de réduire les dépendances cachées:
dans dir/foo.cc
, dont le but principal est de mettre en œuvre ou de tester la substance dans dir2/foo2.h
, votre commande comprend ce qui suit:
-
dir2/foo2.h
(emplacement privilégié - voir les détails ci-dessous). - C système de fichiers.
- C++ système de fichiers.
- d'Autres bibliothèques .h fichiers.
- votre projet .h fichiers.
selon la taille de votre projet, en regardant les graphiques inclus créés par Doxygen (avec l'option INCLUDE_GRAPH
sur ) peut être utile.
un gros problème avec la technique supprimer un en-tête et recompiler est qu'il peut conduire à la compilation immobile, mais code erroné ou inefficace.
-
Modèle de spécialisation: Si vous avez une spécialisation de modèle pour un type qui est dans un en-tête et la plus générale du modèle dans un autre, la suppression de la spécialisation peut laisser le code compilable état, mais avec des résultats non désirés.
-
résolution de surcharge: un problème similaire - si vous avez deux surcharges d'une fonction dans des en-têtes différents, mais qui prennent des types quelque peu compatibles, vous pouvez finir par supprimer la version qui est le mieux adapté dans un cas, mais ont toujours le code compiler. C'est probablement moins probable que la version de spécialisation du modèle, mais c'est possible.
j'ai pensé à écrire quelque chose qui compile chaque fichier sans en-tête individuellement plusieurs temps, à chaque fois enlever un #inclure déclaration. Continuer à faire cela jusqu'à ce qu'un l'ensemble minimal de inclusions est atteint.
je pense que c'est malavisé, et va conduire à" insuffisant mais se trouve à travailler " inclure des ensembles.
supposons que votre fichier source utilise numeric_limits
, mais inclut aussi un fichier d'en-tête, que, pour des raisons qui lui sont propres, inclut <limits>
. Cela ne signifie pas que votre fichier source ne devrait pas inclure <limits>
. Cet autre fichier d'en-tête n'est probablement pas documenté pour définir tout ce qui est défini dans <limits>
, il se trouve que c'est le cas. Un jour, il pourrait s'arrêter: peut-être qu'il utilise une seule valeur comme paramètre par défaut d'une fonction, et peut-être que la valeur par défaut varie de std::numeric_limits<T>::min()
à 0. Et maintenant votre fichier source ne se compile plus, et le responsable de ce fichier d'en-tête Je ne savais même pas que votre dossier existait jusqu'à ce qu'il casse sa construction.
à moins que vous n'ayez des problèmes de construction invalidants en ce moment, je pense que la meilleure façon de supprimer les inclusions redondantes est juste de prendre l'habitude de regarder la liste chaque fois que vous touchez un fichier pour la maintenance. Si vous trouvez que vous avez des douzaines d'inclusions, et après avoir examiné le fichier vous ne pouvez toujours pas comprendre ce que chacun est pour, envisager de se décomposer en fichiers plus petits.
si vous utilisez Visual Studio compiler, vous pouvez essayer /showIncludes option compilateur et ensuite analyser ce qu'il émet à stderr. MSDN: "Le compilateur affiche la liste des fichiers à inclure. Les fichiers include imbriqués sont également affichés (fichiers qui sont inclus à partir des fichiers que vous incluez)."
Yup. Nous avons notre propre préprocesseur qui nous donne accès à notre propre langage macro. Il vérifie également que les fichiers d'en-tête sont inclus qu'une fois. La création d'un simple préprocesseur de vérification pour 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 inclusions qui ne sont pas nécessaires ainsi que les inclusions qui sont nécessaires mais qui sont indirectement incluses par une autre inclusivité.
regardez le projet cppclean . Bien qu'ils n'aient pas encore implémenté cette fonctionnalité, mais c'est prévu.
du site du projet:
CppClean tente de trouver des problèmes dans C++ source qui ralentissent le développement surtout dans les grandes bases de code. Il est similaire à lint; cependant, CppClean se concentre sur la découverte des problèmes inter-modules mondiaux plutôt que problèmes locaux similaires à d'autres les outils d'analyse statique.
le but est de trouver des problèmes qui ralentissent le développement dans les grandes bases de code qui sont modifiés au fil du temps en laissant le code inutilisé. Ce code peut entrer beaucoup de formes des fonctions inutilisées, des méthodes, des membres de données, des types, etc. à inutile #inclure des directives. Inutile # inclut peut causer des compilations supplémentaires considérables augmentant le cycle d'édition-compilation-exécution.
et notamment 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- n'est pas nécessaire si les classes ont été déclarées à la place
- (prévu) fichiers sources qui font référence à des en-têtes qui ne sont pas directement #included, c'est-à-dire des fichiers qui reposent sur un #Include transitif à partir d'un autre en-tête
ici vous pouvez trouver un miroir à BitBucket.
si vous codez dans Eclipse avec CDT, vous pouvez utiliser la commande Organize Includes. Il suffit d'appuyer sur Ctrl+Shift+O et il ajoutera les inclusions nécessaires et supprimera celles qui ne sont pas nécessaires.
je crée habituellement un fichier source (main.c, par exemple) et un fichier d'en-tête pour ce fichier source (main.h.) Dans le fichier source, j'ai mis toutes les principales fonctions "d'interface" que j'utilise dans ce fichier (dans le fichier principal, ce serait main()
), et ensuite quelles que soient les fonctions que j'obtiens après avoir modifié ces fonctions (détails d'implémentation), allez ci-dessous. Dans le fichier d'en-tête, je déclare quelques fonctions extern
qui sont définies dans d'autres fichiers source, mais utilisées dans le fichier source qui utilise ce qui suit: tête. Ensuite, je déclare les structures ou autres types de données que j'utilise dans ce fichier source.
alors je les compile et les lie tous ensemble. Il reste beau et propre. Une inclusion typique ... section, dans mon projet actuel ressemble à cela
#include<windows.h>
#include<windowsx.h>
#include<stdio.h>
#include"interface.h"
#include"thissourcefile.h"
//function prototypes
//source
il y a un en-tête interface
qui garde la trace des structures de données que j'utilise dans toutes les formes du projet, et puis thissourcefile.h
qui fait exactement ce que je viens d'expliquer (déclare externs
, etc.).
de plus, je n'ai jamais défini quoi que ce soit dans mes en-têtes, je n'y ai mis que des déclarations. De cette façon, ils peuvent être inclus par différents fichiers source et toujours lier avec succès. Les prototypes de fonctions (externes, statiques, ou autres) et les déclarations vont dans l'en-tête, de cette façon ils peuvent être utilisés beaucoup de fois--les définitions vont dans la source, parce qu'ils n'ont besoin d'être qu'à un seul endroit.
ce serait évidemment différent si vous créez un bibliothèque, ou quelque chose comme ça. Mais juste pour le projet interne de liaison, je trouve que cela garde tout beau et propre. De plus, si vous écrivez un makefile, (ou que vous utilisez simplement un IDE), la compilation est vraiment simple et efficace.