¿Cómo debo detectar archivos #include innecesarios en un gran proyecto de C ++?

Estoy trabajando en un gran proyecto de C ++ en Visual Studio 2008, y hay muchos archivos con directivas #include innecesarias. Algunas veces, los #include son solo artefactos y todo se comstackrá bien; se eliminan, y en otros casos las clases podrían declararse hacia delante y el #include podría moverse al archivo .cpp . ¿Hay alguna buena herramienta para detectar estos dos casos?

Aunque no revelará archivos de inclusión innecesarios, Visual Studio tiene una configuración /showIncludes (haga clic con el botón derecho en un archivo .cpp , Properties->C/C++->Advanced ) que generará un árbol con todos los archivos incluidos en el momento de la comstackción. Esto puede ayudar a identificar archivos que no deberían ser incluidos.

También puedes echarle un vistazo al idioma pimpl para permitirte escapar de menos dependencias de archivos de cabecera para que sea más fácil ver el fragmento que puedes eliminar.

PC Lint funciona bastante bien para esto, y también encuentra todo tipo de otros problemas tontos para ti. Tiene opciones de línea de comandos que se pueden usar para crear herramientas externas en Visual Studio, pero he descubierto que es más fácil trabajar con el complemento Visual Lint . Incluso la versión gratuita de Visual Lint ayuda. Pero dale una oportunidad a PC-Lint. Configurarlo para que no le dé demasiadas advertencias lleva un poco de tiempo, pero se sorprenderá de lo que aparece.

Hay una nueva herramienta basada en Clang, include-what-you-use , que tiene como objective hacer esto.

!!¡¡RENUNCIA!! Trabajo en una herramienta comercial de análisis estático (no PC Lint). !!¡¡RENUNCIA!!

Hay varios problemas con un enfoque simple no analítico:

1) Conjuntos de sobrecarga:

Es posible que una función sobrecargada tenga declaraciones provenientes de diferentes archivos. ¡Es posible que al eliminar un archivo de encabezado se elija una sobrecarga diferente en lugar de un error de comstackción! El resultado será un cambio silencioso en la semántica que puede ser muy difícil de rastrear después.

2) Especializaciones de plantillas:

De forma similar al ejemplo de sobrecarga, si tiene especializaciones parciales o explícitas para una plantilla, quiere que todas estén visibles cuando se utiliza la plantilla. Es posible que las especializaciones para la plantilla primaria estén en diferentes archivos de encabezado. La eliminación del encabezado con la especialización no causará un error de comstackción, pero puede dar como resultado un comportamiento indefinido si se hubiera seleccionado esa especialización. (Ver: Visibilidad de la especialización de plantillas de la función C ++ )

Como señala ‘msalters’, realizar un análisis completo del código también permite el análisis del uso de clases. Al verificar cómo se usa una clase a través de una ruta específica de archivos, es posible que la definición de la clase (y por lo tanto todas sus dependencias) se elimine por completo o al menos se mueva a un nivel más cercano a la fuente principal en el include árbol.

No conozco ninguna de esas herramientas, y he pensado en escribir una en el pasado, pero resulta que este es un problema difícil de resolver.

Digamos que su archivo fuente incluye ah y bh; ah contiene #define USE_FEATURE_X y bh usa #ifdef USE_FEATURE_X . Si #include "ah" está comentado, su archivo aún puede comstackrse, pero puede no hacer lo que espera. La detección de esto programáticamente no es trivial.

Cualquiera que sea la herramienta, esto necesitaría conocer también su entorno de comstackción. Si ah se ve así:

 #if defined( WINNT ) #define USE_FEATURE_X #endif 

Entonces USE_FEATURE_X solo se define si se define WINNT , por lo que la herramienta necesitaría saber qué directivas genera el propio comstackdor y cuáles se especifican en el comando de comstackción en lugar de en un archivo de encabezado.

Como Timmermans, no estoy familiarizado con ninguna herramienta para esto. Pero conozco progtwigdores que escribieron una secuencia de comandos de Perl (o Python) para tratar de comentar cada una de las líneas de una en una y luego comstackr cada archivo.


Parece que ahora Eric Raymond tiene una herramienta para esto .

El cpplint.py de Google tiene una regla de “incluir lo que usa” (entre muchos otros), pero por lo que puedo decir, no “incluya solo lo que usa”. Aun así, puede ser útil.

Si está interesado en este tema en general, le recomendamos consultar el diseño de software de C ++ a gran escala de Lakos. Está un poco anticuado, pero entra en muchos problemas de “diseño físico” como encontrar el mínimo absoluto de encabezados que deben incluirse. Realmente no he visto este tipo de cosas discutidas en ningún otro lado.

Si sus archivos de encabezado generalmente comienzan con

 #ifndef __SOMEHEADER_H__ #define __SOMEHEADER_H__ // header contents #endif 

(en lugar de usar #pragma una vez) puedes cambiar eso a:

 #ifndef __SOMEHEADER_H__ #define __SOMEHEADER_H__ // header contents #else #pragma message("Someheader.h superfluously included") #endif 

Y dado que el comstackdor genera el nombre del archivo cpp que se está comstackndo, eso le permitirá saber al menos qué archivo cpp está causando que el encabezado ingrese varias veces.

Prueba Include Manager . Se integra fácilmente en Visual Studio y visualiza tus rutas de inclusión que te ayudan a encontrar cosas innecesarias. Internamente usa Graphviz pero hay muchas más características geniales. Y a pesar de que es un producto comercial, tiene un precio muy bajo.

Puede construir un gráfico de inclusión utilizando C / C ++ Incluir vigilante de dependencias de archivo , y encontrar innecesario incluye visualmente.

PC-Lint puede hacer esto. Una manera fácil de hacerlo es configurarlo para detectar solo archivos de inclusión no utilizados e ignorar todos los demás problemas. Esto es bastante sencillo: para habilitar solo el mensaje 766 (“Archivo de encabezado no utilizado en el módulo”), solo incluya las opciones -w0 + e766 en la línea de comando.

El mismo enfoque también se puede utilizar con mensajes relacionados como 964 (“Archivo de encabezado no utilizado directamente en el módulo”) y 966 (“Archivo de encabezado incluido indirectamente no utilizado en el módulo”).

FWIW Escribí sobre esto con más detalle en una publicación de blog la semana pasada en http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318 .

Si está buscando eliminar archivos #include innecesarios para disminuir los tiempos de comstackción, es mejor invertir su tiempo y dinero en paralelizar el proceso de comstackción usando cl.exe / MP , make -j , Xoreax IncrediBuild , distcc / icecream , etc.

Por supuesto, si ya tiene un proceso de comstackción paralelo y aún está tratando de acelerarlo, entonces, por supuesto, limpie sus directivas #include y elimine esas dependencias innecesarias.

Comience con cada archivo incluido y asegúrese de que cada archivo incluido solo incluya lo necesario para comstackrse. Todos los archivos incluidos que luego faltan para los archivos C ++ se pueden agregar a los archivos C ++.

Para cada archivo de inclusión y de origen, comente cada archivo de inclusión de uno en uno y vea si se comstack.

También es una buena idea ordenar alfabéticamente los archivos de inclusión y, cuando esto no sea posible, agregar un comentario.

Agregar uno o ambos de los siguientes #defines excluirá archivos de encabezado a menudo innecesarios y puede mejorar sustancialmente los tiempos de comstackción, especialmente si el código que no utiliza las funciones de la API de Windows.

 #define WIN32_LEAN_AND_MEAN #define VC_EXTRALEAN 

Consulte http://support.microsoft.com/kb/166474

Si aún no lo está, usar un encabezado precomstackdo para incluir todo lo que no va a cambiar (encabezados de plataforma, encabezados de SDK externos o piezas estáticas ya completadas de su proyecto) hará una gran diferencia en los tiempos de comstackción.

http://msdn.microsoft.com/en-us/library/szfdksca(VS.71).aspx

Además, aunque puede ser demasiado tarde para su proyecto, organizar su proyecto en secciones y no agrupar todos los encabezados locales en un encabezado principal grande es una buena práctica, aunque requiere un poco más de trabajo.

Si trabaja con Eclipse CDT, puede probar http://includator.com para optimizar su estructura de inclusión. Sin embargo, es posible que Includator no sepa lo suficiente sobre las inclusiones predefinidas de VC ++ y configurar CDT para usar VC ++ con las inclusiones correctas aún no está integrado en CDT.

El último Jetbrains IDE, CLion, muestra automáticamente (en gris) las inclusiones que no se usan en el archivo actual.

También es posible tener la lista de todas las inclusiones no utilizadas (y también funciones, métodos, etc.) del IDE.

Algunas de las respuestas existentes afirman que es difícil. Eso es cierto, porque necesita un comstackdor completo para detectar los casos en los que sería apropiada una statement directa. No puede analizar C ++ sin saber lo que significan los símbolos; la gramática es simplemente demasiado ambigua para eso. Debe saber si un nombre determinado nombra una clase (podría declararse hacia adelante) o una variable (no puede). Además, debe ser consciente del espacio de nombres.

Tal vez un poco tarde, pero una vez encontré un script de Perl de WebKit que hizo justo lo que quería. Necesitará un poco de adaptación, creo (no estoy muy versado en Perl), pero debería ser el truco:

http://trac.webkit.org/browser/branches/old/safari-3-2-branch/WebKitTools/Scripts/find-extra-includes

(Esta es una twig antigua porque trunk ya no tiene el archivo)

Si hay un encabezado particular que crees que ya no es necesario (por ejemplo, string.h), puedes comentar que incluir y luego poner esto debajo de todo lo que incluye:

 #ifdef _STRING_H_ # error string.h is included indirectly #endif 

Por supuesto, los encabezados de su interfaz pueden usar una convención #define diferente para registrar su inclusión en la memoria CPP. O ninguna convención, en cuyo caso este enfoque no funcionará.

Entonces reconstruye. Hay tres posibilidades:

  • Construye bien string.h no comstack-critical, y se puede eliminar.

  • Los viajes #error. string.g fue incluido indirectamente de alguna manera Aún no sabes si string.h es obligatorio. Si es necesario, debe incluirlo # directamente (ver a continuación).

  • Obtienes algún otro error de comstackción. string.h fue necesario y no se incluye indirectamente, por lo que la inclusión fue correcta para empezar.

Tenga en cuenta que dependiendo de la inclusión indirecta cuando su .h o .c usa directamente otro .h es casi seguro que es un error: de hecho está prometiendo que su código solo requerirá ese encabezado siempre que lo requiera otro encabezado, que probablemente no es lo que querías decir.

Las advertencias mencionadas en otras respuestas sobre los encabezados que modifican el comportamiento en lugar de declarar cosas que causan fallas de comstackción se aplican aquí también.