Estoy trabajando en un gran proyecto de C++ en Visual Studio 2008, y hay muchos archivos con directivas #include
innecesarias. A veces, los #include
s son solo artefactos y todo se compilará bien con ellos eliminados, y en otros casos, las clases podrían ser declaradas hacia adelante y el #include podría moverse al archivo .cpp
. ¿Existen buenas herramientas para detectar estos dos casos?
Si bien no mostrará archivos de inclusión innecesarios, Visual Studio tiene una configuración /showIncludes
(clic derecho en un archivo .cpp
, Properties->C/C++->Advanced
) que generará un árbol de todos los archivos incluidos en el momento de compilación. Esto puede ayudar a identificar archivos que no deberían ser incluidos.
También puedes echarle un vistazo al lenguaje pimpl para que te salgas con menos dependencias de archivos de encabezado para que sea más fácil ver el cruft 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. La configuración para que no le dé demasiadas advertencias lleva un poco de tiempo, pero le sorprenderá lo que aparece.
Hay una nueva herramienta basada en Clang, include-what-you-use , que apunta a hacer esto.
!!¡¡RENUNCIA!! Trabajo en una herramienta de análisis estático comercial (no PC Lint). !!¡¡RENUNCIA!!
Hay varios problemas con un enfoque simple que no analiza:
1) Conjuntos de sobrecarga:
Es posible que una función sobrecargada tenga declaraciones que provienen de diferentes archivos. ¡Es posible que al eliminar un archivo de encabezado se elija una sobrecarga diferente en lugar de un error de compilació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:
Al igual que en el ejemplo de sobrecarga, si tiene especializaciones parciales o explícitas para una plantilla, desea que todas estén visibles cuando se use la plantilla. Puede ser que las especializaciones para la plantilla principal se encuentren en diferentes archivos de encabezado. Eliminar el encabezado con la especialización no causará un error de compilación, pero puede resultar en un comportamiento indefinido si se hubiera seleccionado esa especialización. (Ver: Visibilidad de la especialización de plantillas de la función C++ )
Como lo señaló 'msalters', realizar un análisis completo del código también permite el análisis del uso de la clase. 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 pueda eliminar por completo o, al menos, moverse a un nivel más cercano a la fuente principal en la inclusión árbol.
No conozco ninguna de estas 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 a.h y b.h; a.h contiene #define USE_FEATURE_X
y b.h usa #ifdef USE_FEATURE_X
. Si se comenta #include "a.h"
, su archivo aún puede compilarse, pero no puede hacer lo que espera. Detectar esto programáticamente no es trivial.
Cualquiera sea la herramienta que haga esto, también necesitaría conocer su entorno de construcción. Si a.h se ve como:
#if defined( WINNT )
#define USE_FEATURE_X
#endif
Entonces, USE_FEATURE_X
solo se define si WINNT
está definido, por lo que la herramienta necesitaría saber qué directivas genera el propio compilador y cuáles se especifican en el comando de compilación en lugar de en un archivo de encabezado.
Al igual que Timmermans, no estoy familiarizado con ninguna herramienta para esto. Pero he conocido a los programadores que escribieron un script en Perl (o Python) para intentar comentar cada línea de inclusión uno a la vez y luego compilar cada archivo.
Parece que ahora Eric Raymond tiene una herramienta para esto .
Google cpplint.py tiene una regla de "incluir lo que usas" (entre muchas otras), pero por lo que puedo decir, no "incluye solo Lo que usas ". Aun así, puede ser útil.
Si está interesado en este tema en general, puede consultar Lakos ' Large Scale C++ Software Design . Es un poco anticuado, pero tiene 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 lugar.
Dar Include Manager un intento. Se integra fácilmente en Visual Studio y visualiza tus rutas de inclusión que te ayudan a encontrar cosas innecesarias. Internamente utiliza Graphviz pero hay muchas más características interesantes. Y aunque es un producto comercial tiene un precio muy bajo.
Puede crear un gráfico de inclusión utilizando C/C++ Incluir observador de dependencias de archivos , y encontrar los que no sean necesarios visualmente.
PC-Lint puede hacer esto. Una forma fácil de hacer esto es configurarlo para que detecte los archivos de inclusión no utilizados e ignore todos los demás problemas. Esto es bastante sencillo: para habilitar solo el mensaje 766 ("Archivo de encabezado no utilizado en el módulo"), simplemente incluya las opciones -w0 + e766 en la línea de comandos.
El mismo enfoque también se puede utilizar con mensajes relacionados como 964 ("Archivo de encabezado que no se usa directamente en el módulo") y 966 ("Archivo de encabezado incluido indirectamente no se usa 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=2009_09_01_archive.xml#3575027665614976318 .
Si sus archivos de cabecera generalmente comienzan con
#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#endif
(en lugar de usar #pragma una vez), puedes cambiarlo a:
#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#else
#pragma message("Someheader.h superfluously included")
#endif
Y dado que el compilador genera el nombre del archivo cpp que se está compilando, eso le permitirá saber al menos qué archivo cpp está provocando que el encabezado aparezca varias veces.
Si está buscando eliminar archivos #include
innecesarios para disminuir los tiempos de compilación, su tiempo y dinero podrían gastarse mejor paralelizando su proceso de compilación utilizando cl.exe/MP , make -j , Xoreax IncrediBuild , distcc/ helado , etc.
Por supuesto, si ya tiene un proceso de compilación en paralelo y aún está intentando acelerarlo, entonces limpie sus directivas #include
y elimine las dependencias innecesarias.
Comience con cada archivo de inclusión y asegúrese de que cada archivo de inclusión solo incluya lo necesario para compilarse. Cualquier archivo de inclusión que falte para los archivos de C++ se puede agregar a los archivos de C++.
Para cada archivo de inclusión y origen, comente cada uno de los archivos de inclusión uno por uno y vea si se compila.
También es una buena idea ordenar los archivos de inclusión alfabéticamente y, cuando esto no sea posible, agregue un comentario.
Agregar uno o los dos #defines siguientes excluirá a menudo archivos de encabezado innecesarios y puede mejorar sustancialmente los tiempos de compilación, especialmente si el código no utiliza las funciones de la API de Windows.
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
Si aún no lo ha hecho, el uso de un encabezado precompilado para incluir todo lo que no va a cambiar (encabezados de plataforma, encabezados de SDK externos o partes estáticas ya completadas de su proyecto) hará una gran diferencia en los tiempos de desarrollo.
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 gran encabezado principal es una buena práctica, aunque requiere un poco de trabajo adicional.
El último IDE de Jetbrains, CLion, muestra automáticamente (en gris) los elementos que no se utilizan en el archivo actual.
También es posible tener la lista de todos los elementos no utilizados (y también funciones, métodos, etc.) del IDE.
Si trabajas con Eclipse CDT, puedes probar http://includator.com para optimizar tu estructura de inclusión. Sin embargo, es posible que Includator no sepa lo suficiente sobre las aplicaciones predefinidas de VC++ y que la configuración de CDT para usar VC++ con las aplicaciones correctas aún no esté integrada en CDT.
Tal vez un poco tarde, pero una vez encontré un script de WebKit Perl que hacía justo lo que querías. Necesitaré algo de adaptación, creo (no estoy bien versado en Perl), pero debería hacer el truco:
(esta es una rama vieja porque el tronco ya no tiene el archivo)
Si hay un encabezado en particular que crees que ya no es necesario (digamos string.h), puedes comentar que incluya y luego ponga esto debajo de todos los incluidos:
#ifdef _STRING_H_
# error string.h is included indirectly
#endif
Por supuesto, los encabezados de su interfaz pueden usar una convención diferente #define para registrar su inclusión en la memoria CPP. O ninguna convención, en cuyo caso este enfoque no funcionará.
Luego reconstruir. Hay tres posibilidades:
Se construye bien. string.h no fue crítico para la compilación, y la inclusión para él puede ser eliminada.
Los viajes de #error. string.g se incluyó indirectamente de alguna manera Todavía no sabe si string.h es necesario. Si es necesario, debe # incluirlo directamente (ver más abajo).
Obtienes algún otro error de compilación. string.h era necesario y no se incluye de forma indirecta, por lo que la inclusión era correcta para empezar.
Tenga en cuenta que dependiendo de la inclusión indirecta cuando su .h o .c usa otro .h es casi seguramente un error: en efecto, está prometiendo que su código solo requerirá ese encabezado siempre y cuando algún otro encabezado que esté utilizando lo requiera que probablemente no es lo que quisiste decir.
Las advertencias mencionadas en otras respuestas sobre encabezados que modifican el comportamiento en lugar de declarar cosas que causan fallas de compilación también se aplican aquí.
Algunas de las respuestas existentes afirman que es difícil. De hecho, es cierto, porque necesita un compilador completo para detectar los casos en que una declaración de reenvío sería apropiada. No puedes analizar C++ sin saber qué significan los símbolos; La gramática es simplemente demasiado ambigua para eso. Debe saber si un determinado nombre asigna un nombre a una clase (podría declararse hacia adelante) o una variable (no puede). Además, debe ser consciente del espacio de nombres.