programa funciones fuente estructura ejemplos crear codigos codigo cabeceras cabecera basicos archivos abierto c++ coding-style code-separation

funciones - Código de C++ en archivos de encabezado



crear archivos de cabecera en c (17)

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 del motivo por el que me gusta este estilo probablemente tiene que ver con todos los años que pasé codificando Modula-2 y Ada, ambos con un esquema similar con archivos de especificación y 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 utilizan ahora para C ++.

No soy tan ágil como solía ser, así que no estoy realmente ansioso por meterme en este carro hasta que vea a algunas personas más allí con él. Entonces, ¿qué tan común es este modismo realmente?

Solo para darle un poco de estructura a las respuestas: ¿Es ahora el Camino , muy común, algo común, poco común o loco?


¿Realmente eso 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 clases en classname.h
Código de clase en classnameCode.h
código ejecutable en classname.cpp

Esto divide las simulaciones creadas por el usuario 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 es proporcionar a los usuarios una base de código.



Como dijo Tuomas, tu encabezado debe ser mínimo. Para completarlo, me expandiré un poco.

Yo personalmente uso 4 tipos de archivos en mis proyectos de C++ :

  • Público:
  • Encabezado de reenvío: en el caso de las plantillas, etc., este archivo obtiene las declaraciones de reenvío que aparecerán en el encabezado.
  • Encabezado: este archivo incluye el encabezado de reenvío, si lo hay, y declara todo lo que deseo que sea público (y define las clases ...)
  • Privado:
  • Encabezado privado: este archivo es un encabezado reservado para la implementación, incluye el encabezado y declara las funciones / estructuras auxiliares (para Pimpl, por ejemplo, o predicados). Omita si no es necesario.
  • Archivo de origen: incluye el encabezado privado (o el encabezado si no hay un encabezado privado) y define todo (sin plantilla ...)

Además, comparo esto con otra regla: no defina lo que puede reenviar declarar. Aunque, por supuesto, soy razonable allí (usar Pimpl en todas partes es bastante complicado).

Significa que prefiero una declaración directa sobre una directiva #include en mis encabezados cada vez que puedo salirse con la suya.

Finalmente, también utilizo una regla de visibilidad: limito el alcance de mis símbolos tanto como sea posible para que no contaminen los ámbitos externos.

Poniéndolo en conjunto:

// example_fwd.hpp // Here necessary to forward declare the template class, // you don''t want people to declare them in case you wish to add // another template symbol (with a default) later on class MyClass; template <class T> class MyClassT; // example.hpp #include "project/example_fwd.hpp" // Those can''t really be skipped #include <string> #include <vector> #include "project/pimpl.hpp" // Those can be forward declared easily #include "project/foo_fwd.hpp" namespace project { class Bar; } namespace project { class MyClass { public: struct Color // Limiting scope of enum { enum type { Red, Orange, Green }; }; typedef Color::type Color_t; public: MyClass(); // because of pimpl, I need to define the constructor private: struct Impl; pimpl<Impl> mImpl; // I won''t describe pimpl here :p }; template <class T> class MyClassT: public MyClass {}; } // namespace project // example_impl.hpp (not visible to clients) #include "project/example.hpp" #include "project/bar.hpp" template <class T> void check(MyClass<T> const& c) { } // example.cpp #include "example_impl.hpp" // MyClass definition

El salvavidas aquí es que la mayoría de las veces el encabezado hacia adelante es inútil: solo es necesario en caso de typedef o template y también lo es el encabezado de implementación;)


Creo que es absolutamente absurdo poner TODAS tus 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 ver una clase para consultar cómo usarla, debe mirar el archivo de encabezado. El archivo de encabezado debe proporcionar una lista de lo que puede hacer (se comenta para describir los detalles de cómo usar cada función) y debe incluir una lista de las variables de miembro. NO DEBERÍA incluir CÓMO se implementa cada función individual, porque eso es una carga de información innecesaria y solo desordena el archivo de encabezado.


Creo que su compañero de trabajo es inteligente y usted también es correcto.

Las cosas útiles que encontré que poner todo en los encabezados es que:

  1. No es necesario escribir y sincronizar encabezados y fuentes.

  2. La estructura es simple y no existen dependencias circulares que obliguen al codificador a crear una estructura "mejor".

  3. Portátil, fácil de integrar a un nuevo proyecto.

Estoy de acuerdo con el problema del tiempo de compilación, pero creo que deberíamos notar que:

  1. 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.

  2. La velocidad de compilación es mucho más rápida que antes. Y si tiene un proyecto que debe construirse con un tiempo prolongado y de alta frecuencia, puede indicar que el diseño de su proyecto tiene fallas. Separe las tareas en diferentes proyectos y el módulo puede evitar este problema.

Por último, solo quiero apoyar a tu compañero de trabajo, solo en mi opinión personal.


Creo que su compañero de trabajo tiene razón siempre y cuando no ingrese en el proceso para escribir código ejecutable en el encabezado. El balance correcto, creo, es seguir el camino indicado por GNAT Ada, donde el archivo .ads brinda una definición de interfaz del paquete perfectamente adecuada para sus usuarios y para sus hijos.

Por cierto Ted, ¿has echado un vistazo a este foro a la pregunta reciente sobre el enlace de Ada a la biblioteca CLIPS que escribiste hace varios años y que ya no está disponible (las páginas web relevantes ya están cerradas). Incluso si se realiza en una versión anterior de Clips, este enlace podría ser un buen ejemplo de inicio para alguien que esté dispuesto a utilizar el motor de inferencia CLIPS dentro de un programa Ada 2012.


El código de la plantilla debe estar solo en encabezados. Aparte de eso, todas las definiciones, excepto las líneas, deben estar en .cpp. El mejor argumento para esto sería la implementación estándar de la biblioteca que sigue la misma regla. No estaría en desacuerdo con los desarrolladores de std lib tendrían razón al respecto.


El código en los encabezados generalmente es una mala idea, ya que fuerza la recompilación de todos los archivos que incluyen el encabezado cuando se 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 instanciando en otros archivos cpp.


El día en que los programadores de C ++ acuerden el Camino , los corderos se acostarán con los leones, los palestinos abrazarán a los israelíes, y los gatos y perros podrán casarse.

La separación entre los archivos .h y .cpp es en su mayoría arbitraria en este punto, un vestigio de las optimizaciones del compilador desde hace mucho tiempo. A mi juicio, las declaraciones pertenecen al encabezado y las definiciones pertenecen al archivo de implementación. Pero, eso es solo hábito, no religión.


En mi humilde opinión, tiene mérito SOLO si está haciendo plantillas y / o metaprogramación. Hay muchas razones ya mencionadas que limitan los archivos de encabezado a declaraciones justas. Son solo eso ... encabezados. Si desea incluir código, compílelo como una biblioteca y vincúlelo.


Generalmente, cuando escribo una nueva clase, coloco todo el código en la clase, así no tengo que buscar en otro archivo. Después de que todo está funcionando, rompo el cuerpo de los métodos en el archivo cpp , dejando los prototipos en el archivo hpp.


Lo que podría estar informándole a su compañero de trabajo es la noción de que la mayoría del código C ++ debe ser modelado para permitir la máxima usabilidad. Y si está modelado, todo tendrá que estar en un archivo de cabecera, para que el código del cliente pueda verlo e instanciarlo. 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 dónde viene.


Para agregar más diversión, puede agregar archivos .ipp que contengan la implementación de la plantilla (que se está incluyendo en .hpp ), mientras que .hpp contiene la interfaz.

Como aparte del código de plantilla (dependiendo del proyecto esto puede ser una mayoría o una minoría de archivos) existe un código normal y aquí es mejor separar las declaraciones y las definiciones. Proporcione también declaraciones de reenvío cuando sea necesario; esto puede tener efecto en el tiempo de compilación.


Personalmente hago esto en mis archivos de encabezado:

// class-declaration // inline-method-declarations

No me gusta mezclar el código de los métodos con la clase ya que me resulta doloroso buscar las cosas rápidamente.

No pondría TODOS los métodos en el archivo de encabezado. El compilador (normalmente) no podrá alinear métodos virtuales y (probablemente) solo alinee métodos pequeños sin bucles (depende totalmente del compilador).

Hacer los métodos en la clase es válido ... pero desde un punto de vista de reajuste no me gusta. Poner los métodos en el encabezado significa que, cuando sea posible, se insertarán.


Puse toda la implementación fuera de la definición de la clase. Quiero tener los comentarios doxygen fuera de la definición de la clase.


Si esta nueva forma 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 del encabezado. El código en los encabezados probablemente necesite algún otro encabezado para ser incluido, lo que requerirá otro encabezado y así sucesivamente. Si nos vemos obligados a usar plantillas, tratamos de evitar tirar demasiado los encabezados con plantillas.

También utilizamos el patrón "puntero opaco" cuando corresponda.

Con estas prácticas podemos hacer construcciones 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án grandes reconstrucciones.


Su compañero de trabajo está equivocado, la forma común es y siempre ha sido poner el código en archivos .cpp (o la extensión que desee) y las declaraciones en los encabezados.

Ocasionalmente, hay algo de mérito en 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 sus 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 objetos circulares (a veces deseados) cuando todo el código son los encabezados.

En pocas palabras, tenías razón, él está equivocado.

EDIT: he estado pensando en tu pregunta. Hay un caso en que lo que dice es verdad. plantillas. Muchas de las bibliotecas "modernas" más nuevas, como la promoción, hacen un uso intensivo de las plantillas y, a menudo, son solo "encabezado". Sin embargo, esto solo debe hacerse al tratar con plantillas, ya que es la única forma de hacerlo cuando se trata de ellas.

EDITAR: A algunas personas les gustaría un poco más de aclaración, aquí hay algunas ideas sobre los inconvenientes de escribir el código "solo encabezado":

Si busca alrededor, verá a mucha gente tratando de encontrar una forma 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 de 1K con boost incluido. 14s puede parecer que no está "explotando", pero sin duda es mucho más larga que lo normal y puede sumar bastante rápidamente. Cuando se trata de un gran proyecto. Las bibliotecas solo de encabezado afectan los tiempos de compilación de una manera bastante medible. Simplemente lo toleramos porque el impulso es tan útil.

Además, hay muchas cosas que no se pueden hacer solo en los encabezados (incluso boost tiene bibliotecas para las que es necesario vincular ciertas partes como hilos, sistema de archivos, etc.). Un ejemplo primario es que no puede tener objetos globales simples en libs solo de encabezado (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 que este ejemplo particular sea factible en el futuro.

Como punto final, cuando usamos boost como ejemplo de código solo de encabezado, a menudo se pierde un gran detalle.

Boost es una biblioteca, no un código de nivel de usuario. por lo 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 monumental de tiempo (y no es el caso para las bibliotecas que no cambian de compilar a compilar). Cuando divide las cosas entre el encabezado / fuente y, aún mejor, usa las declaraciones de reenvío para reducir las inclusiones, puede ahorrar horas de recompilación cuando se las agrega a lo largo de un día.