Estoy limpiando las inclusiones en un proyecto de C ++ en el que estoy trabajando, y sigo preguntándome si debería incluir explícitamente todos los encabezados utilizados directamente en un archivo en particular, o si solo debería incluir lo mínimo.
Aquí hay un ejemplo Entity.hpp
:
#include "RenderObject.hpp"
#include "Texture.hpp"
struct Entity {
Texture texture;
RenderObject render();
}
(Supongamos que una declaración directa para RenderObject
no es una opción).
Ahora, sé que eso RenderObject.hpp
incluye Texture.hpp
, lo sé porque cada uno RenderObject
tiene un Texture
miembro. Aún así, incluyo explícitamente Texture.hpp
en Entity.hpp
, porque no estoy seguro de si es una buena idea confiar en él se incluye en RenderObject.hpp
.
Entonces: ¿Es una buena práctica o no?
#ifndef _RENDER_H #define _RENDER_H ... #endif
.#pragma once
resuelve, ¿no?Respuestas:
Siempre debe incluir todos los encabezados que definan los objetos utilizados en un archivo .cpp en ese archivo, independientemente de lo que sepa sobre lo que hay en esos archivos. Debes incluir protectores en todos los archivos de encabezado para asegurarte de que incluir encabezados varias veces no importe.
Las razones:
Texture
objetos en este archivo.RenderObject.hpp
realidad no se necesita aTexture.hpp
sí mismo.Un corolario es que nunca debe incluir un encabezado en otro encabezado a menos que sea explícitamente necesario en ese archivo.
fuente
La regla general es: incluye lo que usas. Si usa un objeto directamente, incluya su archivo de encabezado directamente. Si usa un objeto A que usa B pero no usa B usted mismo, solo incluya Ah
Además, mientras estamos en el tema, solo debe incluir otros archivos de encabezado en su archivo de encabezado si realmente lo necesita en el encabezado. Si solo lo necesita en el .cpp, solo inclúyalo allí: esta es la diferencia entre una dependencia pública y privada, y evitará que los usuarios de su clase arrastren encabezados que realmente no necesitan.
fuente
Sí.
Nunca se sabe cuándo pueden cambiar esos otros encabezados. Tiene todo el sentido del mundo incluir, en cada unidad de traducción, los encabezados que sabe que necesita la unidad de traducción.
Tenemos protectores de encabezado para garantizar que la doble inclusión no sea perjudicial.
fuente
Las opiniones difieren en esto, pero soy de la opinión de que cada archivo (ya sea el archivo fuente c / cpp o el archivo de encabezado h / hpp) debería poder compilarse o analizarse por sí solo.
Como tal, todos los archivos deben # incluir todos y cada uno de los archivos de encabezado que necesitan; no debe suponer que un archivo de encabezado ya se ha incluido anteriormente.
Es un verdadero problema si necesita agregar un archivo de encabezado y descubrir que usa un elemento definido en otro lugar, sin incluirlo directamente ... así que debe buscar (¡y posiblemente terminar con el incorrecto!)
Por otro lado, no importa (como regla general) si #incluye un archivo que no necesita ...
Como punto de estilo personal, organizo los archivos #include en orden alfabético, divididos en sistema y aplicación, esto ayuda a reforzar el mensaje "autónomo y totalmente coherente".
fuente
Depende de si esa inclusión transitiva es por necesidad (por ejemplo, clase base) o debido a un detalle de implementación (miembro privado).
Para aclarar, la inclusión transitiva es necesaria cuando la eliminación solo se puede hacer después de cambiar primero las interfaces declaradas en el encabezado intermedio. Dado que ya es un cambio importante, cualquier archivo .cpp que lo use debe verificarse de todos modos.
Ejemplo: Ah está incluido por Bh, que es utilizado por C.cpp. Si Bh usó Ah para algunos detalles de implementación, entonces C.cpp no debería suponer que Bh continuará haciéndolo. Pero si Bh usa Ah para una clase base, entonces C.cpp puede suponer que Bh continuará incluyendo los encabezados relevantes para sus clases base.
Usted ve aquí la ventaja real de NO duplicar las inclusiones de encabezado. Digamos que la clase base utilizada por Bh realmente no pertenecía a Ah y se refactoriza en Bh. Bh ahora es un encabezado independiente. Si C.cpp incluye redundantemente Ah, ahora incluye un encabezado innecesario.
fuente
Puede haber otro caso: tienes Ah, Bh y tu C.cpp, Bh incluye Ah
entonces en C.cpp, puedes escribir
Entonces, si no escribe #include "Ah" aquí, ¿qué puede pasar? en su C.cpp, se utilizan tanto A como B (por ejemplo, clase). Más tarde, cambió su código C.cpp, eliminó las cosas relacionadas con B, pero dejó Bh incluido allí.
Si incluye tanto Ah como Bh y ahora en este punto, las herramientas que detectan inclusiones innecesarias pueden ayudarlo a señalar que Bh include ya no es necesario. Si solo incluye Bh como se indicó anteriormente, es difícil para las herramientas / humanos detectar la inclusión innecesaria después del cambio de código.
fuente
Estoy adoptando un enfoque similar ligeramente diferente de las respuestas propuestas.
En los encabezados, siempre incluya solo un mínimo, solo lo que se necesita para que la compilación pase. Utilice la declaración hacia adelante siempre que sea posible.
En los archivos de origen, no es tan importante cuánto incluya. Mis preferencias todavía incluyen un mínimo para que pase.
Para proyectos pequeños, incluidos los encabezados aquí y allá, no habrá diferencia. Pero para proyectos medianos a grandes, puede convertirse en un problema. Incluso si se utiliza el último hardware para compilar, la diferencia puede ser notable. La razón es que el compilador todavía tiene que abrir el encabezado incluido y analizarlo. Por lo tanto, para optimizar la compilación, aplique la técnica anterior (incluya un mínimo, y use la declaración directa).
Aunque un poco anticuado, el diseño de software C ++ a gran escala (por John Lakos) explica todo esto en detalle.
fuente
Una buena práctica es no preocuparse por su estrategia de encabezado siempre que se compile.
La sección de encabezado de su código es solo un bloque de líneas que nadie debería mirar hasta que obtenga un error de compilación fácil de resolver. Entiendo el deseo de un estilo "correcto", pero ninguna de las dos formas puede describirse realmente como correcta. La inclusión de un encabezado para cada clase es más probable que cause errores de compilación molestos basados en el orden, pero esos errores de compilación también reflejan problemas que la codificación cuidadosa podría solucionar (aunque posiblemente no valga la pena el tiempo para solucionarlo).
Y sí, tendrá esos problemas basados en pedidos una vez que comience a entrar
friend
tierra.Puedes pensar en el problema en dos casos.
Caso 1: tiene un pequeño número de clases que interactúan entre sí, digamos menos de una docena. Regularmente agrega, elimina y modifica de otra manera estos encabezados, de manera que pueden afectar sus dependencias entre sí. Este es el caso que sugiere su ejemplo de código.
El conjunto de encabezados es lo suficientemente pequeño como para que no sea complicado resolver los problemas que surjan. Cualquier problema difícil se soluciona reescribiendo uno o dos encabezados. Preocuparse por su estrategia de encabezado es resolver problemas que no existen.
Caso 2: Tienes docenas de clases. Algunas de las clases representan la columna vertebral de su programa, y reescribir sus encabezados lo obligaría a reescribir / recompilar una gran cantidad de su código base. Otras clases usan esta columna vertebral para lograr cosas. Esto representa un entorno empresarial típico. Los encabezados se extienden por los directorios y no puede recordar de manera realista los nombres de todo.
Solución: en este punto, debe pensar en sus clases en grupos lógicos y reunir esos grupos en encabezados que eviten que tenga que
#include
repetir una y otra vez. Esto no solo simplifica la vida, sino que también es un paso necesario para aprovechar los encabezados precompilados .Se termina
#include
ing clases que no necesita, pero a quién le importa ?En este caso, su código se vería como ...
fuente