Varias clases en un archivo de encabezado frente a un único archivo de encabezado por clase


Por cualquier razón, nuestra empresa tiene una guía de codificación que establece:

Each class shall have it's own header and implementation file.

Así que si escribimos una clase llamada MyString necesitaríamos un asociado MyStringh.h y myString.cxx .

¿Alguien más hace esto? ¿Alguien ha visto alguna repercusión en el rendimiento de la compilación como resultado? ¿Compila 5000 clases en 10000 archivos tan rápido como 5000 clases en 2500 archivos? Si no, ¿ se nota la diferencia?

[Codificamos C++ y usamos GCC 3.4.4 como nuestro compilador diario]

Author: Charles Menguy, 2008-08-26

12 answers

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 correspondiente .h archivo del mismo nombre.

Normalmente 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 y así sucesivamente. La idea es que las unidades de traducción están aisladas de tal manera que, cuando una unidad de traducción cambia, no tiene que reconstruir muchas cosas, como tendrías que hacer si empezaras a agrupar muchas abstracciones en una sola unidad de traducción.

También encontrará que muchos errores/diagnósticos 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 archivos y clases. (O supongo que se podría llamar una correspondencia de 2 a 1).

 63
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-26 14:34:39

Abrumado por miles de líneas de código?

Tener un conjunto de archivos de cabecera/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 habiendo jugado con las fuentes siguiendo la filosofía "armemos todo", la conclusión es que solo el que escribió el archivo tiene alguna esperanza de no perderse en su interior. Incluso con un IDE, es fácil perderse cosas porque cuando estás jugando con un fuente de 20.000 líneas, usted acaba de cerrar su mente para cualquier cosa que no se refiera exactamente a su problema.

Ejemplo de la vida real: la jerarquía de clases definida en esas fuentes de mil líneas se cerró en una herencia de diamante, y algunos métodos fueron anulados en clases secundarias por métodos con exactamente el mismo código. Esto fue fácilmente pasado por alto (¿quién quiere explorar/comprobar 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 universal como exceptuado.

¿Las dependencias se vuelven circulares?

Tuve este problema con el código de plantilla, pero vi problemas similares con el código C++ y C regular.

Desglosar sus fuentes en 1 encabezado por estructura / clase le permite:

  • Acelere la compilación porque puede usar symbol forward-declaration en lugar de incluir objetos enteros
  • Tienen 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 fuente controlado, las dependencias de clase podrían conducir a un movimiento regular de clases arriba y abajo del archivo, solo para hacer que la cabecera se compile. 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 más fácil estudiar su evolución a través de diferentes versiones diffs

Para mi programa de plantillas, tuve que dividir mi encabezados en dos archivos: El .Archivo HPP que contiene la declaración/definición de clase de plantilla, y el .Archivo INL que contiene las definiciones de dichos métodos de clase.

Poner todo este código dentro de una y solo una cabecera única significaría poner las definiciones de clase al principio de este archivo, y las definiciones de método al final.

Y luego, si alguien necesita solo una pequeña parte del código, con la solución de un solo encabezado, todavía tendría que pagar por el más lento recopilacion.

(§) Tenga en cuenta que puede tener dependencias circulares entre clases si sabe qué clase posee cuál. Esta es una discusión sobre las clases que tienen conocimiento de la existencia de otras clases, no shared_ptr circular dependencies antipattern.

Una última palabra: Los encabezados deben ser autosuficientes

Una cosa, sin embargo, que debe ser respetada por una solución de múltiples encabezados y múltiples fuentes.

Cuando se incluye un encabezado, no importa cuál encabezado, su fuente debe compilar limpiamente.

Cada cabecera debe ser autosuficiente. Se supone que debe desarrollar código, no buscar tesoros, al greping su proyecto de más de 10,000 archivos fuente para encontrar qué encabezado define el símbolo en el encabezado de 1,000 líneas que necesita incluir solo por una enumeración.

Esto significa que cada encabezado define o declara todos los símbolos que usa, o incluye todos los encabezados necesarios (y solo los necesarios cabecera).

Pregunta sobre dependencias circulares

El guion bajo-d pregunta:

¿Puede explicar cómo el uso de encabezados separados hace alguna diferencia con las dependencias circulares? No creo que lo haga. Podemos crear trivialmente una dependencia circular incluso si ambas clases están completamente declaradas en la misma cabecera, simplemente declarando una por adelantado antes de declarar un handle en la otra. Todo lo demás parece ser grandes puntos, pero la idea de que separar los encabezados facilitan 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. Un). Vamos también camino los métodos de la clase A (resp. B) en realidad llamar a los métodos de B (resp. Un).

Tiene una dependencia circular tanto en la definición de las clases, como en las implementaciones de sus métodos.

Si A y B eran normales clases, y los métodos de A y B estaban en .Archivos CPP, no habría ningún problema: Usaría una declaración forward, tendría un encabezado para las definiciones de cada clase, luego cada CPP incluiría ambos HPP.

Pero como tienes plantillas, en realidad tienes que reproducir los patrones anteriores, pero solo con encabezados.

Esto significa:

  1. un encabezado de definición A. def.hpp and B. def.hpp
  2. un encabezado de implementación A. inl.hpp and B. inl.hpp
  3. por conveniencia, un " ingenuo" cabecera A. hpp y B. hpp

Cada cabecera tendrá los siguientes rasgos:

  1. En A. def.hpp (resp. B. def.hpp), tiene una declaración directa de clase B (resp. A), que le permitirá declarar un puntero/referencia a esa clase
  2. A. inl.hpp (resp. B. inl.hpp) incluirá tanto A. def.hpp and B. def.hpp, que habilitará métodos de A (resp. B) utilizar la clase B (resp. Un).
  3. A. hpp (resp. B. hpp) incluirá directamente tanto A. def.hpp and A. inl.hpp (resp. B. def.hpp and B. inl.hpp)
  4. Por supuesto, todos los encabezados deben ser autosuficientes y protegidos por protectores de encabezado

El usuario ingenuo incluirá A. hpp y/o B. hpp, ignorando así todo el lío.

Y tener esa organización significa que el escritor de la biblioteca puede resolver las dependencias circulares entre A y B mientras mantiene ambas clases en archivos separados, fáciles de navegar una vez que entienda el esquema.

Tenga en cuenta que se trataba de un caso de borde (dos plantillas conocerse). Espero que la mayoría del código no necesite ese truco.

 58
Author: paercebal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-19 18:48:19

Hacemos eso en el trabajo, es más fácil encontrar cosas si la clase y los archivos tienen el mismo nombre. En cuanto al rendimiento, realmente no deberías tener 5000 clases en un solo proyecto. Si lo haces, podría ser necesario refactorizar algo.

Dicho esto, hay instancias en las que tenemos varias clases en un archivo. Y eso es cuando es solo una clase auxiliar privada para la clase principal del archivo.

 10
Author: Magnus Westin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-26 14:23:12

+1 para la separación. Acabo de llegar a un proyecto donde algunas clases están en archivos con un nombre diferente, o agrupadas con otra clase, y es imposible encontrarlas de una manera rápida y eficiente. Puede lanzar más recursos a una compilación - no puede recuperar el tiempo perdido del programador porque no puede encontrar el archivo correcto para editar.

 7
Author: Chris Marasti-Georg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-26 14:28:28

Además de ser simplemente "más claro", separar las clases en archivos separados hace que sea más fácil para varios desarrolladores no pisar los dedos de los pies de los demás. Habrá menos fusiones cuando llegue el momento de confirmar cambios en su herramienta de control de versiones.

 7
Author: Matt Dillard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-26 14:34:40

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 tallar algo en piedra sin justificación real.

En cuanto a su pregunta sobre los archivos fuente, no es tanto tiempo para compilar, sino más bien un problema de ser capaz de encontrar el fragmento de código relevante en primer lugar. No todo el mundo está usando un IDE. Y sabiendo que solo buscas a MyClass.h y MyClass.cpp realmente ahorra tiempo comparado con la ejecución de " grep MyClass *.(h / cpp) " sobre un montón de archivos y luego filtrar el #include MyClass.frases h...

Tenga en cuenta que hay soluciones para el impacto de un gran número de archivos fuente en los tiempos de compilación. Vea Diseño de Software C++ a gran Escala por John Lakos para una discusión interesante.

También te gustaría leer Code Complete de Steve McConnell para un excelente capítulo sobre pautas de codificación. Actualmente, este libro es una gran lectura que sigo viniendo volver a regularly

 5
Author: Rob Wells,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-19 09:12:16

La mejor práctica, como otros han dicho, es colocar a cada clase en su propia unidad de traducción desde una perspectiva de mantenimiento y comprensión del código. Sin embargo, en sistemas a gran escala esto a veces no es aconsejable - vea la sección titulada "Hacer que los archivos fuente sean más grandes" en este artículo de Bruce Dawson para una discusión de las compensaciones.

 3
Author: Brian Stewart,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-27 16:41:01

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 de eso tratar de fusionar el .h que se utilizan comúnmente juntos para mejorar el rendimiento si realmente lo necesita. Todo se reduce a dependencias entre archivos y esto es muy específico para cada proyecto.

 2
Author: kokos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-26 14:25:31

He encontrado estas directrices particularmente útiles cuando se trata de archivos de encabezado : http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Header_Files

 2
Author: Francois,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-18 12:46:40

Es muy útil tener solo una clase por archivo, pero si hace su compilación a través de archivos bulkbuild que incluyen todos los archivos C++ individuales, hace que las compilaciones sean más rápidas, ya que el tiempo de inicio es relativamente grande para muchos compiladores.

 1
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-18 05:12:13

Me sorprende que casi todo el mundo esté 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 los nombres de archivo y clase sincronizados. Cada vez que cambie el nombre de una clase, también debe cambiar el nombre del archivo, lo que significa que también debe realizar un cambio en todos los lugares donde se incluya el archivo.

Personalmente agrupo clases relacionadas en un solo archivo y luego le doy a dicho archivo un nombre significativo que no tendrá que cambiar incluso si cambia el nombre de una clase. Tener menos archivos también facilita el desplazamiento a través de un árbol de archivos. Uso Visual Studio en Windows y Eclipse CDT en Linux, y ambos tienen teclas de acceso directo que te llevan directamente a una declaración de clase, por lo que encontrar una declaración de clase es fácil y rápido.

Dicho esto, 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 podría extraer clases y colocarlas en distintas .h y .archivos cpp. Pero no veo esto como esencial.

La elección también depende del tipo de proyecto en el que se trabaje. En mi opinión, el problema no merece una respuesta en blanco y negro ya que cualquiera de las opciones tiene pros y contras.

 1
Author: Peter Ritter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-07 14:33:45

La misma regla se aplica aquí, pero señala algunas excepciones donde se permite así:

  • Árboles de herencia
  • Clases que solo se utilizan dentro de un muy ámbito limitado
  • Algunas Utilidades se colocan simplemente en un 'utils general.h '
 0
Author: Huppie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-26 14:30:11