headers - ejemplos de archivos de cabecera en lenguaje c++
Varias clases en un archivo de encabezado frente a un Ășnico archivo de encabezado por clase (12)
Encontré estas pautas particularmente útiles cuando se trata de archivos de encabezado: http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Header_Files
Por alguna razón, nuestra empresa tiene una guía de codificación que establece:
Each class shall have it''s own header and implementation file.
Entonces, si escribimos una clase llamada MyString
, necesitaríamos un MyStringh.h y MyString.cxx asociados.
¿Alguien más hace ésto? ¿Alguien ha visto alguna repercusión en el rendimiento de compilación como resultado? ¿5000 clases en 10000 archivos se compilan tan rápido como 5000 clases en 2500 archivos? Si no, ¿la diferencia es notable?
[Codificamos C ++ y usamos GCC 3.4.4 como nuestro compilador de todos los días]
+1 para la separación. Acabo de entrar en un proyecto en el que algunas clases están en archivos con un nombre diferente, o agrupadas con otra clase, y es imposible encontrarlas de manera rápida y eficiente. Puede lanzar más recursos en una compilación: no puede recuperar el tiempo perdido del programador porque no puede encontrar el archivo correcto para editar.
Además de simplemente ser "más claro", separar las clases en archivos separados hace que sea más fácil para múltiples desarrolladores no pisar los dedos de los demás. Habrá menos fusión cuando llegue el momento de realizar cambios en su herramienta de control de versiones.
El término aquí es unidad de traducción y realmente desea (si es posible) tener una clase por unidad de traducción, es decir, una implementación de clase por archivo .cpp, con un archivo .h correspondiente del mismo nombre.
Por lo general, es más eficiente (desde un punto de vista de compilación / enlace) hacer las cosas de esta manera, especialmente si estás haciendo cosas como un enlace incremental, etc. La idea es que las unidades de traducción están aisladas de manera que, cuando una unidad de traducción cambia, no tiene que reconstruir muchas cosas, como lo haría si comenzara a agrupar muchas abstracciones en una sola unidad de traducción.
También encontrará muchos errores / diagnósticos que se informan a través del nombre del archivo ("Error en Myclass.cpp, línea 22") y ayuda si hay una correspondencia uno a uno entre los archivos y las clases. (O supongo que podría llamarlo una correspondencia de 2 a 1).
Es una práctica común hacer esto, especialmente para poder incluir .h en los archivos que lo necesitan. Por supuesto, el rendimiento se ve afectado, pero trate de no pensar en este problema hasta que surja :).
Es mejor comenzar con los archivos separados y después intentar fusionar los .h que se usan comúnmente para mejorar el rendimiento si realmente se necesita. Todo se reduce a las dependencias entre archivos y esto es muy específico para cada proyecto.
Gday,
La mayoría de los lugares donde he trabajado han seguido esta práctica. De hecho, he escrito estándares de codificación para BAE (Aust.) Junto con las razones por las que en lugar de simplemente grabar algo en piedra sin ninguna justificación real.
En cuanto a su pregunta sobre los archivos fuente, no es tanto tiempo para compilar sino más bien un problema para poder encontrar el fragmento de código relevante en primer lugar. No todos están usando un IDE. Y saber que solo buscas MyClass.h y MyClass.cpp realmente ahorra tiempo en comparación con ejecutar "grep MyClass *. (H | cpp)" sobre un grupo de archivos y luego filtrar las sentencias #include MyClass.h ...
Tenga en cuenta que existen soluciones para el impacto de un gran número de archivos fuente en tiempos de compilación. Vea Diseño de software en gran escala en C ++ por John Lakos para una discusión interesante.
También puede leer Code Complete de Steve McConnell para obtener un excelente capítulo sobre las pautas de codificación. En realidad, este libro es una gran lectura que sigo volviendo regularmente
aplausos, Rob
Hacemos eso en el trabajo, es más fácil encontrar cosas si la clase y los archivos tienen el mismo nombre. En cuanto a rendimiento, realmente no debería tener 5000 clases en un solo proyecto. Si lo hace, algunas refactorizaciones podrían estar en orden.
Dicho esto, hay instancias en las que tenemos múltiples clases en un archivo. Y eso es cuando es solo una clase de ayuda privada para la clase principal del archivo.
La misma regla se aplica aquí, pero nota algunas excepciones donde está permitido Al igual que:
- Árboles de herencia
- Clases que solo se usan dentro de un alcance muy limitado
- Algunas utilidades simplemente se colocan en un ''utils.h'' general
La mejor práctica, como han dicho otros, es colocar cada clase en su propia unidad de traducción desde una perspectiva de mantenimiento y comprensibilidad del código. Sin embargo, en sistemas a gran escala, esto a veces no es aconsejable; consulte la sección titulada "Haga que esos archivos fuente sean más grandes" en este artículo de Bruce Dawson para una discusión sobre los intercambios.
Es muy útil tener solo una clase por archivo, pero si haces tu compilación a través de archivos bulkbuild que incluyen todos los archivos C ++ individuales, se obtienen compilaciones más rápidas ya que el tiempo de inicio es relativamente grande para muchos compiladores.
Me sorprende que casi todos estén a favor de tener un archivo por clase. El problema con eso es que en la era de la ''refactorización'' uno puede tener dificultades para mantener el archivo y los nombres de las clases sincronizados. Cada vez que cambia un nombre de clase, también debe cambiar el nombre del archivo, lo que significa que también debe hacer un cambio en todos los lugares donde se incluye el archivo.
Personalmente, grupo las clases relacionadas en un solo archivo y le doy a dicho archivo un nombre significativo que no tendrá que cambiar aunque cambie el nombre de una clase. Tener menos archivos también facilita el desplazamiento por el árbol de archivos. Utilizo Visual Studio en Windows y Eclipse CDT en Linux, y ambos tienen teclas de método abreviado que lo llevan directamente a una declaración de clase, por lo que encontrar una declaración de clase es fácil y rápido.
Habiendo dicho eso, creo que una vez que se completa un proyecto, o su estructura se ha "solidificado", y los cambios de nombre se vuelven raros, puede tener sentido tener una clase por archivo. Ojalá hubiera una herramienta que pudiera extraer clases y ubicarlas en distintos archivos .h y .cpp. Pero no veo esto como esencial.
La elección también depende del tipo de proyecto en el que se trabaja. En mi opinión, el problema no merece una respuesta en blanco y negro, ya que cualquiera de las opciones tiene ventajas y desventajas.
Abrumado por miles de líneas de código?
Tener un conjunto de archivos de encabezado / fuente por clase en un directorio puede parecer exagerado. Y si el número de clases va hacia 100 o 1000, incluso puede ser aterrador.
Pero después de haber jugado con las fuentes siguiendo la filosofía "vamos a armar todo", la conclusión es que solo el que escribió el archivo tiene alguna esperanza de no perderse en el interior. Incluso con un IDE, es fácil pasar por alto las cosas porque cuando juegas con una fuente de 20,000 líneas, simplemente cierras tu mente por algo que no se refiera exactamente a tu problema.
Ejemplo de vida real: la jerarquía de clases definida en esas fuentes de mil líneas se cerró a sí misma en una herencia de diamante, y algunos métodos se anularon en clases secundarias por métodos con exactamente el mismo código. Esto se pasó por alto fácilmente (¿quién quiere explorar / verificar un código fuente de 20,000 líneas?), Y cuando se cambió el método original (corrección de errores), el efecto no fue tan universal como se exceptuó.
Dependencias que se vuelven circulares?
Tuve este problema con el código de plantilla, pero vi problemas similares con el código C ++ y C normal.
Desglosar tus fuentes en 1 encabezado por estructura / clase te permite:
- Acelera la compilación porque puedes usar el símbolo forward-declaration en lugar de incluir objetos enteros
- Tener dependencias circulares entre clases (§) (es decir, la clase A tiene un puntero a B, y B tiene un puntero a A)
En el código controlado por fuente, las dependencias de clase pueden conducir al movimiento regular de las clases hacia arriba y hacia abajo del archivo, solo para hacer que la compilación del encabezado. No desea estudiar la evolución de tales movimientos al comparar el mismo archivo en diferentes versiones.
Tener encabezados separados hace que el código sea más modular, más rápido de compilar y facilita el estudio de su evolución a través de diferentes versiones.
Para mi programa de plantilla, tuve que dividir mis encabezados en dos archivos: el archivo .HPP que contiene la declaración / definición de la clase de plantilla y el archivo .INL que contiene las definiciones de dichos métodos de clase.
Poner todo este código dentro de un único encabezado único significaría poner definiciones de clase al comienzo de este archivo y las definiciones de métodos al final.
Y luego, si alguien necesitara solo una pequeña parte del código, con la solución de un solo encabezado, aún tendría que pagar por la compilación más lenta.
(§) Tenga en cuenta que puede tener dependencias circulares entre clases si sabe qué clase posee qué. Esta es una discusión sobre las clases que tienen conocimiento de la existencia de otras clases, no dependencias circulares shared_ptr antipattern.
Una última palabra: los encabezados deberían ser autosuficientes
Una cosa, sin embargo, que debe ser respetada por una solución de múltiples encabezados y múltiples fuentes.
Cuando incluye un encabezado, no importa qué encabezado, su fuente debe compilarse limpiamente.
Cada encabezado debe ser autosuficiente. Se supone que debes desarrollar código, no búsqueda de tesoros, agregando más de 10,000 proyectos de archivos fuente para encontrar qué encabezado define el símbolo en el encabezado de 1,000 líneas que necesitas incluir solo por una enumeración.
Esto significa que cada encabezado define o reenvía-declara todos los símbolos que usa, o incluye todos los encabezados necesarios (y solo los encabezados necesarios).
Pregunta sobre dependencias circulares
underscore-d pregunta:
¿Puede explicar cómo el uso de encabezados separados hace alguna diferencia a las dependencias circulares? No creo que lo haga Podemos crear trivialmente una dependencia circular incluso si ambas clases se declaran completamente en el mismo encabezado, simplemente declarando hacia adelante una por adelantado antes de declararle un identificador en la otra. Todo lo demás parece ser un gran punto, pero la idea de que los encabezados separados faciliten las dependencias circulares parece muy lejos
underscore_d, 13 de noviembre a las 23:20
Digamos que tienes 2 plantillas de clase, A y B.
Digamos que la definición de clase A (resp. B) tiene un puntero a B (resp. A). También los métodos de la clase A (resp. B) llaman a los métodos de B (resp. A).
Tiene una dependencia circular tanto en la definición de las clases como en la implementación de sus métodos.
Si A y B fueran clases normales, y los métodos A y B estuvieran en archivos .CPP, no habría ningún problema: usaría una declaración directa, tendría un encabezado para cada definición de clase, luego cada CPP incluiría ambos HPP.
Pero como tiene plantillas, en realidad tiene que reproducir los patrones anteriores, pero solo con encabezados.
Esto significa:
- un encabezado de definición A.def.hpp y B.def.hpp
- un encabezado de implementación A.inl.hpp y B.inl.hpp
- por conveniencia, un encabezado "ingenuo" A.hpp y B.hpp
Cada encabezado tendrá los siguientes rasgos:
- En A.def.hpp (o B.def.hpp), tiene una declaración directa de clase B (resp. A), que le permitirá declarar un puntero / referencia a esa clase
- A.inl.hpp (respectivamente B.inl.hpp) incluirá tanto A.def.hpp como B.def.hpp, lo que permitirá que los métodos de A (resp. B) utilicen la clase B (resp. A) .
- A.hpp (respectivamente B.hpp) incluirá directamente tanto A.def.hpp como A.inl.hpp (respectivamente B.def.hpp y B.inl.hpp)
- Por supuesto, todos los encabezados deben ser autosuficientes y estar protegidos por protectores de encabezado
El usuario ingenuo incluirá A.hpp y / o B.hpp, ignorando todo el desastre.
Y tener esa organización significa que el escritor de la biblioteca puede resolver las dependencias circulares entre A y B, manteniendo ambas clases en archivos separados, fácil de navegar una vez que comprenda el esquema.
Tenga en cuenta que se trataba de un caso marginal (dos plantillas se conocían). Espero que la mayoría del código no necesite ese truco.