Declaración directa vs incluir

17

Reduce the number of #include files in header files. It will reduce build times. Instead, put include files in source code files and use forward declarations in header files.

Leí esto aquí. http://www.yolinux.com/TUTORIALS/LinuxTutorialC++CodingStyle.html .

Entonces dice si una clase (clase A) en el archivo de encabezado no necesita usar la definición real de alguna clase (clase B). En ese momento podemos usar la declaración directa en lugar de incluir el archivo de encabezado particular (clase B).

Pregunta: Si la clase (clase A) en el encabezado if no usa la definición real de una clase particular (clase B), entonces, ¿cómo la declaración directa ayuda a reducir el tiempo de compilación?

Nayana Adassuriya
fuente

Respuestas:

11

Al compilador no le importa si la clase A usa la clase B. Solo sabe que cuando se compila la clase A y no tiene una declaración previa de la clase B (declaración directa o no), entra en pánico y la marca como un error.

Lo importante aquí es que el compilador sabe que no trató de compilar un programa después de que su gato caminó en su teclado y creó algunas letras aleatorias que pueden interpretarse o no como una clase.

Cuando ve una inclusión, para poder usar cualquier información contenida dentro, debe abrir el archivo y analizarlo (independientemente de si realmente necesita hacerlo o no). Si ese archivo incluye otros archivos, también deben abrirse y analizarse, etc. Si esto puede evitarse, generalmente es una buena idea utilizar una declaración de reenvío.

Editar : la excepción a esta regla son los encabezados precompilados. En este caso, todos los encabezados se compilan y guardan para futuras compilaciones. Si los encabezados no cambian, el compilador puede usar de manera inteligente los encabezados precompilados de compilaciones anteriores y, por lo tanto, reducir los tiempos de compilación, pero solo funciona bien si a menudo no es necesario cambiar los encabezados.

Neil
fuente
Gracias por la explicación. A continuación, como ejemplo bien le parece que son tres archivos de cabecera vehicle.h, bus.h, toybus.h. vehicle.hincluir porbus.h e bus.hincluir por toybus.h. por lo que si hago algún cambio en bus.h. ¿el compilador se abre y analiza de vehicle.hnuevo? ¿lo compila de nuevo?
Nayana Adassuriya
1
@NayanaAdassuriya Sí, se incluye y analiza cada vez, por eso también ves #pragma once o #ifndef __VEHICLE_H_escribir declaraciones en los archivos de encabezado para evitar que dichos archivos se incluyan varias veces (o se usen varias veces al menos en el caso de ifndef).
Neil
4

porque entonces A.hpp no ​​necesita #incluir B.hpp

entonces A.hpp se convierte

class B;//or however forward decl works for classes

class A
{
    B* bInstance_;
//...
}

así que cuando se incluye A.hpp, entonces B.hpp no ​​se incluye implícitamente y no es necesario volver a compilar todos los archivos que dependen solo de A.hpp cada vez que b.hpp cambia

monstruo de trinquete
fuente
pero en el archivo fuente (A.cpp). debe incluir el archivo de encabezado real (Bh). Entonces cada vez que necesita compilar. Finalmente, en ambos sentidos, Bh necesita recompilarse con los cambios. ¿Algo diferente?
Nayana Adassuriya
@NayanaAdassuriya no porque A solo usa un puntero a B y los cambios a B no afectarán a A.hpp (o los archivos que lo incluyen)
monstruo de trinquete
@NayanaAdassuriya: Sí, A.cpp tendrá que volver a compilar (si usa la definición de B dentro de los cuerpos de los métodos de A, pero generalmente lo hace), pero C.cpp, que usa A, pero no B directamente, no lo hará.
Jan Hudec
3

Recuerde, el preprocesador C / C ++ es un paso de procesamiento separado, puramente textual. La #includedirectiva extrae el contenido del encabezado incluido y el compilador tiene que analizarlo. Por otra parte, la compilación de cada.cpp es completamente separada, por lo que el hecho de que el compilador solo se analice B.hal compilar B.cppno lo ayuda lo más mínimo cuando lo necesita nuevamente al compilar A.cpp. Y de nuevo al compilar C.cpp. Y D.cpp. Y así. Y cada uno de esos archivos tiene que volver a compilarse si algún archivo incluido en él ha cambiado.

Digamos que class Ausa class By classes Cy Duse class A, pero no necesita manipular B. Si la clase Ase puede declarar solo con la declaración directa de B, B.hse compila dos veces: al compilar B.cppy A.cpp(porque Btodavía se necesita dentro Ade los métodos).

Pero cuando se A.hincluye B.h, se compila cuatro veces, al compilar B.cpp, A.cpp, C.cppy D.cppcomo más tarde dos ahora indirectamente incluyen B.htambién.

Además, cuando el encabezado se incluye más de una vez, el preprocesador todavía tiene que leerlo cada vez. Omitirá el procesamiento de su contenido debido a la protección #ifdefs, pero aún así lo lee y necesita buscar el final de la protección, lo que significa que tiene que analizar todas las directivas del preprocesador en su interior.

(Como se mencionó en la otra respuesta, los encabezados precompilados intentan solucionar esto, pero son su propia lata de gusanos; básicamente, puede usarlos razonablemente para los encabezados del sistema y solo si no está usando demasiados, pero no para encabezados en su proyecto)

Jan Hudec
fuente
+1, las cabeceras incluidas solo se convierten en un problema serio cuando tienes una cantidad bastante grande de clases, no cuando tienes solo dos clases A y B. Todas las otras publicaciones parecen perder ese punto central.
Doc Brown
2

Una declaración directa es mucho más rápida de analizar que un archivo de encabezado completo que puede incluir incluso más archivos de encabezado.

Además, si cambia algo en el archivo de encabezado para la clase B, todo, incluido ese encabezado, tendrá que volver a compilarse. Con una declaración de reenvío, ese puede ser solo el archivo fuente en el que reside la implementación de A. Pero si el encabezado de A realmente incluye el encabezado de B, todo incluido a.hpptambién se recompilará, incluso si no usa nada de B.

Benjamin Kloster
fuente