2009-08-06 22 views
9

He leído todo lo que pude encontrar sobre este tema, incluyendo un par de discusiones muy útiles en este sitio, las pautas de codificación de la NASA y las pautas de Google C++. Incluso compré el libro "Diseño físico en C++" recomendado aquí (lo siento, olvidé el nombre) y obtuve algunas ideas útiles de eso. La mayoría de las fuentes parecen estar de acuerdo: los archivos de encabezado deben ser independientes, es decir, incluyen lo que necesitan para que un archivo cpp pueda incluir el encabezado sin incluir otros y se compilaría. También entiendo el punto acerca de la declaración progresiva en lugar de incluir siempre que sea posible.Política de encabezado C++ en proyectos grandes (redux)

Dicho esto, ¿qué tal si foo.cpp incluye bar.h y qux.h, pero resulta que sí incluye bar.hqux.h? ¿Debería foo.cpp luego evitar incluir qux.h? Pro: limpia foo.cpp (menos "ruido"). Con: si alguien cambia bar.h para dejar de incluir qux.h, foo.cpp misteriosamente comienza a fallar en la compilación. También hace que la dependencia entre foo.cpp y qux.h no sea obvia.

Si su respuesta es "un archivo cpp debe #incluir cada encabezado que necesita", llevado a su conclusión lógica que significa que casi todos los archivos cpp tienen #include <string>, <cstddef> etc. ya que la mayoría del código terminará usándolos, y Si no se supone que dependas de otro encabezado que los incluya, tu cpp debe incluirlos explícitamente. Parece mucho "ruido" en los archivos cpp.

¿Pensamientos?

discusiones anteriores:

What are some techniques for limiting compilation dependencies in C++ projects?

Your preferred C/C++ header policy for big projects?

How do I automate finding unused #include directives?

ETA: Inspirado en las discusiones anteriores de aquí, he escrito un script Perl para comentar sucesivamente a cabo cada 'incluye' y 'usar', luego intente recompilar el archivo fuente, para descubrir lo que no se necesita. También descubrí cómo integrarlo con VS 2005, por lo que puede hacer doble clic para ir a "no utilizado". Si alguien lo quiere, hágamelo saber ... muy experimental en este momento sin embargo.

+0

Mi conjetura es que esto debería ser wiki de la comunidad, ya que es posiblemente subjetivo. – GManNickG

+0

Si recuerda cuál es el verdadero nombre del libro "Diseño físico en C++", edite la pregunta. Me gustaría verificarlo (si no lo he hecho todavía). –

+0

El libro podría ser "Diseño de software de C++ a gran escala" de John Lakos: http://www.amazon.com/Large-Scale-Software-Addison-Wesley-Professional-Computing/dp/0201633620 –

Respuesta

8

Si su respuesta es "un archivo CPP debe #include cada cabecera que necesita", llevado a su conclusión lógica de que significaría que casi todos los cpp tiene que #include <string>, <cstddef> etc. ya que la mayoría de código va a terminar usando esos, y si se supone que no debe confiar en algún otro encabezado que los incluya, su cpp debe incluirlos explícitamente.

Yup. Así es como yo lo prefiero.

Si el 'ruido' es demasiado fuerte, está bien tener un archivo de inclusión 'global' que contenga el conjunto común habitual de includes (como stdafx.h en muchos programas de Windows) e incluirlo en el inicio de cada archivo .cpp (que también ayuda con los encabezados precompilados).

+0

En realidad, la cosa stdafx es un patrón que estoy tratando de evitar. Un problema es que nuestro código debe ser multiplataforma, y ​​por lo que puedo ver los pch no funcionan de la misma manera en VC++ y gcc. Además, terminamos con una complejidad de inclusión "n squared" ya que todo terminó incluyendo todo, lo que lleva a largos tiempos de compilación. Creo que podríamos incluir STL en un lugar común, pero eso significa que cada unidad de compilación los obtiene, quiera o no. También noté que los encabezados STL se incluyen entre sí, por lo que si incluye (por ejemplo) 'cadena', es posible que no necesite 'memoria' (solo un ejemplo). –

+2

Entiendo por qué no desea usar el patrón stdafx.h, pero puede funcionar y es un patrón que no considero "de mala calidad". Además, no recomendaría tirar todo y el fregadero de la cocina en el encabezado stdafx, solo los encabezados que ** realmente ** se usan prácticamente en todas partes. El archivo .cpp aún puede incluir otros encabezados que con menor frecuencia se necesitan después del archivo stdaf.h.Y no creo que me preocupe la inclusión múltiple de encabezados que de otro modo serían necesarios, no deberían afectar demasiado los tiempos de compilación (la siguiente inclusión de un archivo no se procesará). –

2

Creo que todavía debe incluir ambos archivos. Esto ayuda a mantener el código.

// Foo.cpp 

#include <Library1> 
#include <Library2> 

Puedo leer eso y ver fácilmente qué bibliotecas utiliza.Si Library2 utilizado Library1, y se transformó a esto:

// Foo.cpp 

#include <Library2> 

Pero todavía vi Library1 código, podría ser un poco confuso. No es difícil adivinar que alguna otra biblioteca debe incluirlo, pero todavía es un proceso de pensamiento lo que tiene que ocurrir.

Ser explícito significa que no tengo que adivinar, incluso para un micro segundo de compilación extra.

+0

¿Lo extendería también a las bibliotecas STL? p.ej. si un archivo cpp usa "cadena", ¿le gustaría ver el include? En cada archivo que utiliza "cadena"? Definitivamente puedo ver su razonamiento para los encabezados de los usuarios, pero al mirar mi código, la mayoría de los archivos fuente tendrán que incluir un montón de STL en la parte superior. –

+2

Espera, ¿qué otra cosa harías con ellos? Si su clase utiliza cadenas en el encabezado, iría allí, pero si solo fuera la implementación que lo demandó, iría en el cpp. Cada cpp * debe * incluir el encabezado que necesita. – GManNickG

0

Cada archivo de encabezado proporciona las definiciones necesarias para utilizar un servicio de algún tipo (una definición de clase, algunas macros, lo que sea). Si está utilizando directamente los servicios expuestos a través de un archivo de encabezado, incluya ese archivo de encabezado incluso si otro archivo de encabezado también lo incluye. Por otro lado, si está utilizando esos servicios solo de forma indirecta, solo en el contexto de los servicios del otro archivo de encabezado, no incluya el archivo de encabezado directamente.

En mi opinión, esto no es un gran problema en ambos sentidos. La segunda inclusión de un archivo de encabezado no se analiza por completo; básicamente está comentado para todos, pero la primera vez está incluido. A medida que cambian las cosas y descubre que estaba usando un archivo de encabezado que no incluía directamente, no es un gran problema agregarlo.

2

Creo que deberías incluir ambos hasta que se vuelva insoportable. Luego refactorice sus clases y archivos de origen para reducir el acoplamiento, basándose en que si solo enumera todas sus dependencias es oneroso, entonces probablemente tenga demasiadas dependencias ...

Un compromiso podría ser decir que si algo en bar.h contiene una definición de clase (o declaración de función) que necesariamente requiere otra definición de clase de qux.h, entonces bar.h se puede suponer/requerido para incluir qux.h, y el usuario de la API en bar.h no necesita molestarse incluyendo ambos.

Supongamos que el cliente quiere verse a sí mismo como "realmente" interesado solo en la barra API, pero tiene que usar qux también, porque llama a una función de barra que toma o devuelve un objeto qux por valor. Entonces puede olvidarse que qux tiene su propio encabezado e imaginar que es una barra API definida en bar.h, con qux solo formando parte de esa API.

Esto significa que no puede contar, por ejemplo, buscando archivos cpp para las menciones de qux.h para encontrar todos los clientes de qux. Pero nunca confiaría en eso de todos modos, ya que en general es demasiado fácil equivocarse al no incluir explícitamente una dependencia que ya está indirectamente incluida, y nunca darse cuenta de que no ha enumerado todos los encabezados relevantes. Por lo tanto, probablemente no deba suponer que las listas de encabezado estén completas, sino que use doxygen (o gcc -M, o lo que sea) para obtener una lista completa de las dependencias y búsquelas.

+0

Gracias ... este es un buen punto. Rampant incluye * podría * de hecho ser un olor a código, un signo de acoplamiento excesivo. Definitivamente voy a echarle un vistazo a eso. –

1

¿qué tal si foo.cpp incluye bar.h y qux.h, pero resulta que bar.h incluye qux.h? ¿Debería foo.cpp entonces evitar incluir qux.h?

Si foo.cpp utiliza nada de qux.h directamente, éste debe incluir esta cabecera en sí. De lo contrario, dado que bar.h necesita qux.h, confiaría en bar.h incluyendo todo lo que necesita.

0

Una forma de pensar si foo.cpp usa directamente qux.h, es pensar qué pasaría si foo.cpp ya no necesita incluir bar.h. Si foo.cpp aún necesita incluir qux.h, entonces debe aparecer en una lista explícita. Si ya no necesita nada de qux.h, probablemente no sea necesario incluirlo explícitamente.

0

Aquí se aplica la regla de "nunca optimizar sin perfilar", creo.

(Esta respuesta es parcial hacia "rendimiento" sobre "el desorden"; el desorden general, se puede limpiar a voluntad, hay que admitir que se hace más difícil más adelante, pero el rendimiento que los impactos sobre una base regular.)

Try en ambos sentidos, fíjate si hay alguna mejora de velocidad significativa para tu compilador, y solo entonces considera eliminar los encabezados "duplicados", porque como indican otras respuestas, hay una penalización de mantenibilidad a largo plazo que tomas eliminando los duplicados.

Considere obtener algo como Sysinternals FileMon para ver si está realmente generando éxitos del sistema de archivos para esos duplicados incluidos (la mayoría de los compiladores no lo harán, con los resguardos de encabezado correctos).

Nuestra experiencia aquí indica que la búsqueda activa de cabeceras que son completamente sin usar (o podría ser, con declaraciones adelantadas apropiados) y la eliminación de ellos es mucho más digno de tiempo y esfuerzo que averiguar las cadenas incluyen, por posibles duplicados. Y una buena pelusa (férula, PC-Lint, etc.) puede ayudarlo con esta determinación.

Aún más digno de nuestro tiempo y esfuerzo fue descubrir cómo hacer que nuestro compilador maneje varias unidades de compilación por ejecución (hasta un punto, una aceleración lineal, la compilación estuvo completamente dominada por el inicio del compilador) .

Después de eso, es posible que desee considerar la locura del "único gran CPP". Puede ser bastante impresionante.

Cuestiones relacionadas