Mi estilo personal con C ++ siempre tiene que poner las declaraciones de clase en un archivo de inclusión y las definiciones en un archivo .cpp, muy parecido a lo estipulado en la respuesta de Loki a los archivos de encabezado de C ++, separación de código . Es cierto que parte de la razón por la que me gusta este estilo probablemente tiene que ver con todos los años que pasé codificando Modula-2 y Ada, los cuales tienen un esquema similar con archivos de especificación y archivos de cuerpo.
Tengo un compañero de trabajo, mucho más experto en C ++ que yo, que insiste en que todas las declaraciones de C ++ deben, cuando sea posible, incluir las definiciones allí mismo en el archivo de encabezado. No está diciendo que este sea un estilo alternativo válido, o incluso un estilo ligeramente mejor, sino que este es el nuevo estilo universalmente aceptado que todos usan ahora para C ++.
Ya no soy tan ágil como solía ser, así que no estoy realmente ansioso por subirme a su carro hasta que vea a unas pocas personas más con él. Entonces, ¿qué tan común es realmente este idioma?
Solo para dar una estructura a las respuestas: ¿es ahora The Way , muy común, algo común, poco común o loco?
Respuestas:
Su compañero de trabajo está equivocado, la forma común es y siempre ha sido poner código en archivos .cpp (o cualquier extensión que desee) y declaraciones en los encabezados.
De vez en cuando hay algún mérito para poner código en el encabezado, esto puede permitir una alineación más inteligente por parte del compilador. Pero al mismo tiempo, puede destruir los tiempos de compilación ya que todo el código debe procesarse cada vez que el compilador lo incluye.
Finalmente, a menudo es molesto tener relaciones de objeto circulares (a veces deseadas) cuando todo el código son los encabezados.
En pocas palabras, tenías razón, él está equivocado.
EDITAR: He estado pensando en tu pregunta. Hay un caso donde lo que dice es cierto. plantillas. Muchas bibliotecas "modernas" más nuevas, como boost, hacen un uso intensivo de las plantillas y, a menudo, son "solo encabezado". Sin embargo, esto solo debe hacerse cuando se trata de plantillas, ya que es la única forma de hacerlo cuando se trata de ellas.
EDITAR: algunas personas les gustaría un poco más de aclaración, aquí hay algunas ideas sobre las desventajas de escribir el código "solo encabezado":
Si busca alrededor, verá que muchas personas intentan encontrar una manera de reducir los tiempos de compilación cuando se trata de impulsar. Por ejemplo: Cómo reducir los tiempos de compilación con Boost Asio , que está viendo una compilación de 14s de un solo archivo 1K con boost incluido. Puede que los 14 no parezcan "explotar", pero ciertamente es mucho más largo de lo normal y puede acumularse bastante rápido. Cuando se trata de un gran proyecto. Las bibliotecas de solo encabezado afectan los tiempos de compilación de una manera bastante medible. Simplemente lo toleramos porque el impulso es muy útil.
Además, hay muchas cosas que no se pueden hacer solo en los encabezados (incluso boost tiene bibliotecas a las que debe vincular ciertas partes, como subprocesos, sistema de archivos, etc.). Un ejemplo principal es que no puede tener objetos globales simples en las bibliotecas de encabezado solamente (a menos que recurra a la abominación que es un singleton) ya que se encontrará con múltiples errores de definición. NOTA: Las variables en línea de C ++ 17 harán posible este ejemplo en particular en el futuro.
Como punto final, cuando se usa boost como ejemplo de código de solo encabezado, a menudo se pierde un gran detalle.
Boost es una biblioteca, no un código de nivel de usuario. así que no cambia tan a menudo. En el código de usuario, si coloca todo en los encabezados, cada pequeño cambio hará que tenga que volver a compilar todo el proyecto. Esa es una pérdida de tiempo monumental (y no es el caso de las bibliotecas que no cambian de compilación a compilación). Cuando divide cosas entre encabezado / fuente y mejor aún, use declaraciones directas para reducir las inclusiones, puede ahorrar horas de recompilación cuando se suman a lo largo de un día.
fuente
El día que los codificadores de C ++ estén de acuerdo en The Way , los corderos se acostarán con los leones, los palestinos abrazarán a los israelíes y los gatos y los perros podrán casarse.
La separación entre los archivos .h y .cpp es principalmente arbitraria en este punto, un vestigio de las optimizaciones del compilador hace mucho tiempo. A mi entender, las declaraciones pertenecen al encabezado y las definiciones pertenecen al archivo de implementación. Pero, eso es solo costumbre, no religión.
fuente
El código en los encabezados generalmente es una mala idea, ya que obliga a la recompilación de todos los archivos que incluyen el encabezado cuando cambia el código real en lugar de las declaraciones. También ralentizará la compilación, ya que deberá analizar el código en cada archivo que incluya el encabezado.
Una razón para tener código en los archivos de encabezado es que generalmente se necesita para que la palabra clave en línea funcione correctamente y cuando se usan plantillas que se están instalando en otros archivos cpp.
fuente
Lo que podría estar informando a su compañero de trabajo es la noción de que la mayoría de los códigos C ++ deben tener la plantilla para permitir la máxima usabilidad. Y si tiene una plantilla, entonces todo tendrá que estar en un archivo de encabezado, para que el código del cliente pueda verlo y crear una instancia. Si es lo suficientemente bueno para Boost y STL, es lo suficientemente bueno para nosotros.
No estoy de acuerdo con este punto de vista, pero puede ser de donde viene.
fuente
Creo que tu compañero de trabajo es inteligente y tú también tienes razón.
Lo útil que encontré es que poner todo en los encabezados es que:
No es necesario escribir y sincronizar encabezados y fuentes.
La estructura es simple y ninguna dependencia circular obliga al codificador a hacer una estructura "mejor".
Portátil, fácil de integrar en un nuevo proyecto.
Estoy de acuerdo con el problema del tiempo de compilación, pero creo que deberíamos notar que:
Es muy probable que el cambio del archivo fuente cambie los archivos de encabezado, lo que lleva a que todo el proyecto se vuelva a compilar.
La velocidad de compilación es mucho más rápida que antes. Y si tiene que construir un proyecto con mucho tiempo y alta frecuencia, puede indicar que el diseño de su proyecto tiene fallas. Separar las tareas en diferentes proyectos y módulos puede evitar este problema.
Por último, solo quiero apoyar a tu compañero de trabajo, solo desde mi punto de vista personal.
fuente
A menudo pondré funciones triviales de miembros en el archivo de encabezado, para permitir que se inserten. ¿Pero poner todo el código allí, solo para ser coherente con las plantillas? Eso es una locura.
Recuerde: una consistencia tonta es el duende de las mentes pequeñas .
fuente
A<B>
en un archivo cpp, y luego el usuario quiere unA<C>
?Como dijo Tuomas, tu encabezado debería ser mínimo. Para completar, me expandiré un poco.
Yo personalmente uso 4 tipos de archivos en mis
C++
proyectos:Además, combino esto con otra regla: no defina lo que puede reenviar. Aunque, por supuesto, soy razonable allí (usar Pimpl en todas partes es bastante complicado).
Significa que prefiero una declaración adelantada sobre una
#include
directiva en mis encabezados siempre que pueda salirse con la suya.Finalmente, también uso una regla de visibilidad: limito los alcances de mis símbolos tanto como sea posible para que no contaminen los alcances exteriores.
Poniéndolo en conjunto:
El salvavidas aquí es que la mayoría de las veces el encabezado directo es inútil: solo es necesario en caso de
typedef
o,template
y también lo es el encabezado de implementación;)fuente
Para agregar más diversión, puede agregar
.ipp
archivos que contienen la implementación de la plantilla (que se incluye en.hpp
), mientras que.hpp
contiene la interfaz.Además del código en plantilla (dependiendo del proyecto, puede tratarse de archivos mayoritarios o minoritarios) existe un código normal y aquí es mejor separar las declaraciones y definiciones. Proporcione también declaraciones de reenvío cuando sea necesario; esto puede tener efecto en el tiempo de compilación.
fuente
Generalmente, cuando escribo una nueva clase, pondré todo el código en la clase, para que no tenga que buscar en otro archivo. Después de que todo esté funcionando, descompongo el cuerpo de los métodos en el archivo cpp , dejando los prototipos en el archivo hpp.
fuente
Si este nuevo camino es realmente El Camino , podríamos haber estado corriendo en una dirección diferente en nuestros proyectos.
Porque tratamos de evitar todas las cosas innecesarias en los encabezados. Eso incluye evitar la cascada de encabezados. El código en los encabezados probablemente necesitará incluir otro encabezado, que necesitará otro encabezado, etc. Si nos vemos obligados a usar plantillas, intentamos evitar ensuciar demasiado los encabezados con plantillas.
También usamos el patrón "puntero opaco" cuando corresponde.
Con estas prácticas podemos hacer compilaciones más rápidas que la mayoría de nuestros pares. Y sí ... cambiar el código o los miembros de la clase no causará grandes reconstrucciones.
fuente
Yo personalmente hago esto en mis archivos de encabezado:
No me gusta mezclar el código de los métodos con la clase, ya que me resulta difícil buscar las cosas rápidamente.
No pondría TODOS los métodos en el archivo de encabezado. El compilador (normalmente) no podrá en línea métodos virtuales y (probablemente) solo en línea pequeños métodos sin bucles (depende totalmente del compilador).
Hacer los métodos en la clase es válido ... pero desde un punto de vista legible no me gusta. Poner los métodos en el encabezado significa que, cuando sea posible, se alinearán.
fuente
En mi humilde opinión, tiene mérito SOLO si está haciendo plantillas y / o metaprogramación. Ya hay muchas razones mencionadas que limitan los archivos de encabezado a solo declaraciones. Son solo eso ... encabezados. Si desea incluir código, lo compila como una biblioteca y lo vincula.
fuente
Puse toda la implementación fuera de la definición de clase. Quiero tener los comentarios de doxygen fuera de la definición de clase.
fuente
Creo que es absolutamente absurdo poner TODAS las definiciones de funciones en el archivo de encabezado. ¿Por qué? Porque el archivo de encabezado se usa como la interfaz PÚBLICA para su clase. Es el exterior de la "caja negra".
Cuando necesite mirar una clase para hacer referencia a cómo usarla, debería mirar el archivo de encabezado. El archivo de encabezado debe dar una lista de lo que puede hacer (comentado para describir los detalles de cómo usar cada función), y debe incluir una lista de las variables miembro. NO DEBE incluir CÓMO se implementa cada función individual, porque esa es una carga de información innecesaria y solo llena el archivo de encabezado.
fuente
¿Eso realmente no depende de la complejidad del sistema y de las convenciones internas?
En este momento estoy trabajando en un simulador de red neuronal que es increíblemente complejo, y el estilo aceptado que se espera que use es:
Definiciones de clase en classname.h
Código de clase en classnameCode.h
código ejecutable en classname.cpp
Esto divide las simulaciones creadas por el usuario a partir de las clases base creadas por el desarrollador, y funciona mejor en la situación.
Sin embargo, me sorprendería ver que la gente hace esto en, por ejemplo, una aplicación de gráficos o cualquier otra aplicación cuyo propósito no sea proporcionar a los usuarios una base de código.
fuente
El código de la plantilla debe estar solo en los encabezados. Aparte de eso, todas las definiciones, excepto las en línea, deben estar en .cpp. El mejor argumento para esto sería las implementaciones de la biblioteca estándar que siguen la misma regla. No estaría en desacuerdo con que los desarrolladores de std lib estarían en lo correcto al respecto.
fuente
libstdc++
Parece que el GCC (AFAICS) no pone casi nadasrc
y casi todo dentroinclude
, ya sea que 'deba' estar o no en un encabezado. Así que no creo que esta sea una cita precisa / útil. De todos modos, no creo que stdlibs sea un gran modelo para el código de usuario: obviamente están escritos por codificadores altamente calificados, pero para ser utilizados , no leídos: abstraen la alta complejidad en la que la mayoría de los codificadores no deberían pensar , necesita feo en_Reserved
__names
todas partes para evitar conflictos con el usuario, los comentarios y el espaciado están por debajo de lo que aconsejaría, etc. Son ejemplares de una manera estrecha.Creo que su compañero de trabajo tiene razón siempre que no entre en el proceso de escribir código ejecutable en el encabezado. El equilibrio correcto, creo, es seguir la ruta indicada por GNAT Ada donde el archivo .ads ofrece una definición de interfaz perfectamente adecuada del paquete para sus usuarios y para sus hijos.
Por cierto, Ted, ¿ha echado un vistazo en este foro a la pregunta reciente sobre el enlace de Ada a la biblioteca CLIPS que escribió hace varios años y que ya no está disponible (las páginas web relevantes ahora están cerradas). Incluso si se hace a una versión antigua de Clips, este enlace podría ser un buen ejemplo de inicio para alguien dispuesto a usar el motor de inferencia CLIPS dentro de un programa Ada 2012.
fuente