¿Qué debe incluirse en un archivo .h?

93

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

Enrico Tuvera Jr
fuente
Pregunta relacionada: stackoverflow.com/questions/333889/…
Spoike
7
Este es un problema de estilo puro, pero creo que las declaraciones de C ++ van en un .hpparchivo mientras que las declaraciones de C van en un .harchivo. Esto es muy útil al mezclar código C y C ++ (por ejemplo, módulos heredados en C).
Thomas Matthews
@ThomasMatthews Tiene sentido. ¿Se usa esa práctica con frecuencia?
ty
@lightningleaf: Sí, la práctica se usa a menudo especialmente cuando se mezclan lenguajes C ++ y C.
Thomas Matthews

Respuestas:

113

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

Los 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 las funciones y las variables internas a las que otros módulos nunca deben acceder, son lo que pertenecen a los .cpparchivos. En una palabra, "implementaciones".

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

Ámbar
fuente
4
Excepto que los datos de la clase privada deben ir en el encabezado. Las plantillas deben estar completamente definidas en el encabezado (a menos que use uno de los pocos compiladores compatibles export). La única forma de evitar el n. ° 1 es PIMPL. # 2 sería posible si exportfuera compatible y podría ser posible usando c ++ 0x y externplantillas. En mi opinión, los archivos de encabezado en c ++ pierden gran parte de su utilidad.
KitsuneYMG
23
Todo bien, pero con terminología inexacta. En una palabra, "declaraciones": el término "definición" es sinónimo de "implementación". Solo el código declarativo, el código en línea, las definiciones de macros y el código de plantilla deben estar en un encabezado; es decir, nada que instancia código o datos.
Clifford
8
Tengo que estar de acuerdo con Clifford. Utiliza los términos declaración y definición de forma bastante vaga e intercambiable. Pero tienen significados precisos en C ++. Ejemplos: una declaración de clase introduce el nombre de una clase pero no dice qué contiene. Una definición de clase enumera todos los miembros y funciones de amigos. Ambos se pueden colocar en archivos de encabezado sin problemas. Lo que usted llama "prototipo de función" es una declaración de función . Pero una definición de función es aquello que contiene el código de la función y debe colocarse en un archivo cpp, a menos que esté en línea o (parte de) una plantilla.
sellibitze
5
Tienen significados precisos en C ++, no tienen significados precisos en inglés. Mi respuesta fue escrita en este último.
Ámbar
54

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

¿Qué ve el compilador?

El compilador ve un archivo fuente grande (.cpp) con sus encabezados incluidos correctamente. El archivo fuente es la unidad de compilación que se compilará en un archivo 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 el mal ...

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

Los encabezados se utilizan para poner esos detalles compartidos.

Mover al encabezado las declaraciones de lo que se debe compartir entre múltiples fuentes

¿Nada mas?

En C ++, hay algunas otras cosas que se podrían poner en el encabezado porque también necesitan ser compartidas:

  • código en línea
  • plantillas
  • constantes (generalmente las que desea usar dentro de los interruptores ...)

Mueva al encabezado TODO lo que necesita ser compartido, incluidas las implementaciones compartidas

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

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

  • Declaraciones futuras
  • declaraciones / definición de funciones / estructuras / clases / plantillas
  • implementación de código en línea y con plantilla

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

Los encabezados se pueden dividir en tres partes

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

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

Imaginemos que tenemos un MyObject con plantilla. 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", suele ser menos complicado. La mayor parte del código tendrá solo una organización de fuente / encabezado simple, con algo de código en línea en la fuente.

Pero en otros casos (objetos con plantilla que se conocen entre sí), tenía que tener para cada objeto encabezados de declaración e implementación separados, con una fuente vacía que incluye 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, limitando la cantidad de símbolos analizados a lo estrictamente necesario y evitando la recopilación innecesaria de una fuente que solo se preocupa por la declaración directa cuando cambia la implementación de un método en línea.

Conclusión

Debe hacer que la organización de su código sea lo más simple posible y lo más modular posible. Ponga tanto como sea posible en el archivo fuente. Solo exponga en los encabezados lo que debe compartirse.

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

^ _ ^

paercebal
fuente
17

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

Adrien Plisson
fuente
+1 con una advertencia que puede usar siempre que esté en algún espacio de nombres detallado (o un espacio de nombres anónimo). Pero sí, nunca use usingpara traer cosas al espacio de nombres global en un encabezado.
KitsuneYMG
+1 Este es mucho más fácil de responder. :) Además, los archivos de encabezado no deben contener espacios de nombres anónimos .
sellibitze
Está bien que los archivos de encabezado contengan espacios de nombres anónimos, siempre que comprenda lo que eso significa, es decir, que cada unidad de traducción tendrá una copia diferente de las cosas que defina el espacio de nombres. Las funciones en línea en espacios de nombres anónimos se recomiendan en C ++ para los casos en que las usaría static inlineen C99, debido a algo que tiene que ver con lo que sucede cuando se combinan vínculos internos con plantillas. Los espacios de nombres anon le permiten "ocultar" funciones, al tiempo que preservan el enlace externo.
Steve Jessop
Steve, lo que escribiste no me convenció. Elija un ejemplo concreto en el que crea que un espacio de nombres anon tiene mucho sentido en un archivo de encabezado.
sellibitze
6

Lo que se compila en nada (huella binaria cero) va al archivo de encabezado.

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

las funciones no, pero las funciones en línea sí (o macros), porque producen código solo donde se llaman.

las plantillas no son código, son solo una receta para crear código. por lo que también van en archivos h.

Pavel Radzivilovsky
fuente
1
"funciones en línea ... producen código sólo donde se llama". Eso no es cierto. Las funciones en línea pueden estar o no en línea en los sitios de llamada, pero incluso si están en línea, el cuerpo de la función real todavía existe tal como lo hace para una función no en línea. La razón por la que está bien tener funciones en línea en los encabezados no tiene nada que ver con si generan código, es porque las funciones en línea no activan la regla de una definición, por lo que, a diferencia de las funciones que no están en línea, no hay problemas para vincular dos unidades de traducción diferentes que han incluido el encabezado.
Steve Jessop
3

En general, coloca declaraciones en el archivo de encabezado y 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; consulte ¿Por qué tener archivos de encabezado y archivos .cpp en C ++? y archivos de encabezado C ++, separación de código, por ejemplo.

Comunidad
fuente
por supuesto, también puede poner definiciones de clases en archivos de encabezado. Ni siquiera tienen que ser plantillas.
sellibitze
1

Las declaraciones de su 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).

Alexander Gessler
fuente
1

Principalmente, el archivo de encabezado contiene un esqueleto de clase o una declaración (no cambia con frecuencia)

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

Ashish
fuente
5
Absténgase de utilizar terminología no estándar. ¿Qué es "esqueleto de clase", qué es "implementación de clase"? Además, lo que llama declaración en el contexto de clases probablemente incluye definiciones de clase.
sellibitze
0

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();
}
jose
fuente
0

Esperaría ver:

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

Sin embargo, la respuesta real es qué no poner:

  • Definiciones (puede llevar a que las cosas se definan de forma múltiple)
  • usar declaraciones / directivas (las obliga a cualquiera, incluido su encabezado, puede causar interferencias de nombres)
jk.
fuente
1
Ciertamente, también puede colocar definiciones de clases en archivos de encabezado. Una declaración de clase no dice nada sobre sus miembros.
sellibitze
0

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

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

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

Ahora, por supuesto, estoy hablando del primer subgrupo.

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

Como los carteles anteriores han dicho y mostrado, usted declara áreas de uso público y privado y sus encabezados, esto también incluye variables públicas y privadas. 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.

Filip Ekberg
fuente
0
  • Archivos de encabezado: no deben cambiar durante el desarrollo con demasiada frecuencia -> debe pensar y escribirlos de una vez (en el caso ideal)
  • Archivos de origen: cambios durante la implementación
nothrow
fuente
Esta es una práctica. Para algunos proyectos más pequeños, podría ser el camino a seguir. Pero puede intentar desaprobar funciones y sus prototipos (en archivos de encabezado), en lugar de cambiar su firma o eliminarlos. Al menos hasta cambiar el número mayor. Como cuando 1.9.2 pasa a 2.0.0 beta.
TamusJRoyce
0

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 los hubiera
  • externas a variables globales (si las hay)

Cuerpo (.cpp)

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

Como regla general, colocas la parte "compartida" del módulo en el .h (la parte que otros módulos deben poder ver) y la parte "no compartida" en el .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.

Khelben
fuente
Como regla general, la menor cantidad posible de inclusiones debe estar en el archivo .h, y el archivo .cpp debe incluir los encabezados que necesite. Eso acorta los tiempos de compilación y no contamina los espacios de nombres.
David Thornley