Pregunta ¿Qué debería ir en un archivo .h?


Al dividir el código en varios archivos, ¿qué debería ir exactamente en un archivo .h y qué debería ir en un archivo .cpp?


73
2017-12-22 11:38


origen


Respuestas:


Archivos de encabezado (.h) están diseñados para proporcionar la información que se necesitará en múltiples archivos. Cosas como declaraciones de clase, prototipos de funciones y enumeraciones suelen ir en archivos de encabezado. En una palabra, "definiciones".

Archivos de código (.cpp) están diseñados para proporcionar la información de implementación que solo debe conocerse en un archivo. En general, los cuerpos de función y las variables internas a las que otros módulos deben / nunca deben acceder son los que pertenecen a .cpp archivos. En una palabra, "implementaciones".

La pregunta más simple que debe hacerse para determinar qué pertenece es "si cambio esto, ¿tendré que cambiar el código en otros archivos para que las cosas se vuelvan a compilar?" Si la respuesta es "sí", probablemente pertenezca al archivo de encabezado; si la respuesta es "no", probablemente pertenece al archivo de código.


88
2017-12-22 11:42



El hecho es que, en C ++, esto es algo más complicado que el encabezado C / organización de origen.

¿Qué ve el compilador?

El compilador ve un gran archivo fuente (.cpp) con sus encabezados correctamente incluidos. El archivo de origen es la unidad de compilación que se compilará en un archivo de objeto.

Entonces, ¿por qué son necesarios los encabezados?

Porque una unidad de compilación podría necesitar información sobre una implementación en otra unidad de compilación. Entonces uno puede escribir, por ejemplo, la implementación de una función en una fuente, y escribir la declaración de esta función en otra fuente que necesite usarla.

En este caso, hay dos copias de la misma información. Que es malvado ...

La solución es compartir algunos detalles. Si bien la implementación debe permanecer en el origen, podría ser necesario compartir la declaración de símbolos compartidos, funciones similares o definición de estructuras, clases, enumeraciones, etc.

Los encabezados se usan para poner esos detalles compartidos.

Mueva al encabezado las declaraciones de lo que debe compartirse entre varias fuentes

¿Nada mas?

En C ++, hay otras cosas que se pueden poner en el encabezado porque también se deben compartir:

  • código en línea
  • plantillas
  • constantes (por lo general aquellas que quiere usar dentro de los interruptores ...)

Mover al encabezado TODO lo que se debe compartir, incluidas las implementaciones compartidas

¿Significa entonces que podría haber fuentes dentro de los encabezados?

Sí. De hecho, hay muchas cosas diferentes que podrían estar dentro de un "encabezado" (es decir, compartido entre las fuentes).

  • Declaraciones a futuro
  • declaraciones / definición de funciones / estructuras / clases / plantillas
  • implementación de código en línea y templado

Se vuelve complicado, y en algunos casos (dependencias circulares entre símbolos), imposible de mantener en un encabezado.

Los encabezados se pueden dividir en tres partes

Esto significa que, en un caso extremo, podrías tener:

  • un encabezado de declaración directa
  • un encabezado de declaración / definición
  • un encabezado de implementación
  • una fuente de implementación

Imaginemos que tenemos un MyObject modelado. Nosotros podríamos tener:

// - - - - MyObject_forward.hpp - - - - 
// This header is included by the code which need to know MyObject
// does exist, but nothing more.
template<typename T>
class MyObject ;

.

// - - - - MyObject_declaration.hpp - - - - 
// This header is included by the code which need to know how
// MyObject is defined, but nothing more.
#include <MyObject_forward.hpp>

template<typename T>
class MyObject
{
   public :
      MyObject() ;
   // Etc.
} ;

void doSomething() ;

.

// - - - - MyObject_implementation.hpp - - - - 
// This header is included by the code which need to see
// the implementation of the methods/functions of MyObject,
// but nothing more.
#include <MyObject_declaration.hpp>

template<typename T>
MyObject<T>::MyObject()
{
   doSomething() ;
}

// etc.

.

// - - - - MyObject_source.cpp - - - - 
// This source will have implementation that does not need to
// be shared, which, for templated code, usually means nothing...
#include <MyObject_implementation.hpp>

void doSomething()
{
   // etc.
} ;

// etc.

¡Guauu!

En la "vida real", generalmente es menos complicado. La mayoría del código tendrá solo una organización simple de encabezado / fuente, con algún código en la fuente.

Pero en otros casos (los objetos con plantilla se conocen entre sí), tuve que tener para cada objeto encabezados separados de declaración y ejecución, con una fuente vacía que incluyera esos encabezados solo para ayudarme a ver algunos errores de compilación.

Otra razón para dividir los encabezados en encabezados separados podría ser acelerar la compilación, limitar la cantidad de símbolos analizados a lo estrictamente necesario y evitar la compilación innecesaria de una fuente a la que solo le importa la declaración directa cuando cambia la implementación de un método en línea.

Conclusión

Debe hacer que su organización de código sea lo más simple posible y lo más modular posible. Pon todo lo que puedas en el archivo fuente. Solo exponga en los encabezados lo que se debe compartir.

Pero el día que tenga dependencias circulares entre objetos con plantilla, no se sorprenda si su organización de código se vuelve algo más "interesante" que la organización de encabezado / fuente simple ...

^ _ ^


44
2017-12-22 23:04



Además de todas las demás respuestas, le diré lo que NO coloca en un archivo de encabezado:
using declaración (el ser más común using namespace std;) no deberían aparecer en un archivo de encabezado porque contaminan el espacio de nombre del archivo de origen en el que está incluido.


15
2017-12-22 11:48



Qué compila en nada (cero huella binaria) va al archivo de encabezado.

Las variables no se compilan en nada, pero las declaraciones de tipo sí (porque solo describen cómo se comportan las variables).

las funciones no funcionan, pero las funciones en línea sí (o macros), ya que solo producen código donde se llama.

las plantillas no son código, solo son una receta para crear código. entonces ellos también entran en h archivos.


5
2017-12-22 11:48



En general, coloca declaraciones en el archivo de encabezado y las definiciones en el archivo de implementación (.cpp). La excepción a esto son las plantillas, donde la definición también debe ir en el encabezado.

Esta pregunta y otras similares se han hecho con frecuencia en SO - ver ¿Por qué tienen archivos de encabezado y archivos .cpp en C ++? y Archivos de encabezado C ++, separación de código por ejemplo.


3
2017-12-22 11:42



Sus declaraciones de clase y función más la documentación y las definiciones de funciones / métodos en línea (aunque algunos prefieren ponerlos en archivos .inl separados).


1
2017-12-22 11:40



Principalmente, el archivo de encabezado contiene esqueleto de clase o declaración (no cambia frecuentemente)

y el archivo cpp contiene implementación de clase (cambia con frecuencia).


1
2017-12-22 11:46



el archivo de encabezado (.h) debe ser para declaraciones de clases, estructuras y sus métodos, prototipos, etc. La implementación de esos objetos se realiza en cpp.

en .h

    class Foo {
    int j;

    Foo();
    Foo(int)
    void DoSomething();
}

0
2017-12-22 11:41



Esperaría ver:

  • declaraciones
  • comentarios
  • definiciones marcadas en línea
  • plantillas

la verdadera respuesta es qué no poner:

  • definiciones (pueden llevar a que las cosas se definan de manera múltiple)
  • usar declaraciones / directivas (las fuerza a cualquier persona, incluso a su encabezado, puede causar un par de nombres)

0
2017-12-22 11:42



El encabezado Define algo pero no dice nada sobre la implementación. (Excluyendo plantillas en esta "metafore")

Dicho esto, debe dividir las "definiciones" en subgrupos; en este caso, hay dos tipos de definiciones.

  • Usted define el "diseño" de su estructura, indicando solo todo lo que necesitan los grupos de uso circundantes.
  • Las definiciones de una variable, función y una clase.

Ahora, por supuesto, estoy hablando del primer subgrupo.

El encabezado está ahí para definir el diseño de su estructura con el fin de ayudar al resto del software a usar la implementación. Es posible que desee verlo como una "abstracción" de su implementación, que se dice vagamente, pero creo que se adapta bastante bien en este caso.

Como los carteles anteriores han dicho y demostrado que declaras áreas de uso público y privado y sus encabezados, esto también incluye variables privadas y públicas. Ahora, no quiero entrar en el diseño del código aquí, pero es posible que desee considerar lo que coloca en sus encabezados, ya que esa es la capa entre el usuario final y la implementación.


0
2017-12-22 11:59



Encabezado (.h)

  • Macros e incluye necesarios para las interfaces (el menor número posible)
  • La declaración de las funciones y clases
  • Documentación de la interfaz
  • Declaración de funciones / métodos en línea, si corresponde
  • variables externas a globales (si las hay)

Cuerpo (.cpp)

  • Resto de macros e incluye
  • Incluye el encabezado del módulo
  • Definición de funciones y métodos
  • Variables globales (si las hay)

Como regla general, coloque la parte "compartida" del módulo en la .h (la parte que otros módulos deben poder ver) y la parte "no compartida" en la .cpp

PD: Sí, he incluido variables globales. Los he usado algunas veces y es importante no definirlos en los encabezados, o obtendrás muchos módulos, cada uno definiendo su propia variable.

EDITAR: Modificado después del comentario de David


0
2017-12-22 11:46