¿Qué debería y qué no debería estar en un archivo de encabezado? [cerrado]

71

¿Qué cosas nunca deberían incluirse en un archivo de encabezado?

Si, por ejemplo, estoy trabajando con un formato estándar documentado de la industria que tiene muchas constantes, ¿es una buena práctica definirlas en un archivo de encabezado (si estoy escribiendo un analizador para ese formato)?

¿Qué funciones deben ir en el archivo de encabezado?
¿Qué funciones no deberían?

Moshe Magnes
fuente
1
Breve e indoloro: definiciones y declaraciones necesarias en más de un módulo.
ott--
21
Marcar esta pregunta como "demasiado amplia" y cerrarla es una exageración vergonzosa de moderación. Esta pregunta pregunta exactamente lo que estoy buscando: la pregunta está bien formada y hace una pregunta muy clara: ¿cuáles son las mejores prácticas? Si esto es "demasiado amplio" para la ingeniería de software ... también podríamos cerrar todo este foro.
Gewure
TL; DR. Para C ++, en la cuarta edición de "El lenguaje de programación C ++" escrito por Bjarne Stroustrup (su creador), en la Sección 15.2.2 se describe lo que debe y no debe contener un encabezado. Sé que etiquetó la pregunta a C, pero algunos de los consejos también son aplicables. Creo que esta es una buena pregunta ...
horro

Respuestas:

57

Qué poner en los encabezados:

  • El conjunto mínimo de #includedirectivas necesarias para que el encabezado sea compilable cuando el encabezado se incluye en algún archivo fuente.
  • Definiciones de símbolos del preprocesador de cosas que deben compartirse y que solo pueden lograrse a través del preprocesador. Incluso en C, los símbolos del preprocesador se mantienen mejor al mínimo.
  • Reenviar declaraciones de estructuras que son necesarias para hacer que las definiciones de estructura, prototipos de funciones y declaraciones de variables globales en el cuerpo del encabezado sean compilables.
  • Definiciones de estructuras de datos y enumeraciones que se comparten entre múltiples archivos de origen.
  • Declaraciones para funciones y variables cuyas definiciones serán visibles para el enlazador.
  • Definiciones de funciones en línea, pero tenga cuidado aquí.

Lo que no pertenece en un encabezado:

  • Gratuitos #includedirectivas. Esos gratuitos incluyen la recompilación de cosas que no necesitan ser recompiladas, y a veces pueden hacer que un sistema no pueda compilarse. No haga #includeun archivo en un encabezado si el encabezado en sí no necesita ese otro archivo de encabezado.
  • Símbolos de preprocesador cuya intención podría lograrse mediante algún mecanismo, cualquier mecanismo, que no sea el preprocesador.
  • Montones y montones de definiciones de estructura. Divídalos en encabezados separados.
  • Definiciones en línea de funciones que requieren un adicional #include, que están sujetas a cambios o que son demasiado grandes. Esas funciones en línea deben tener poco o ningún abanico desplegado, y si tienen un abanico desplegado, deben estar localizadas en los elementos definidos en el encabezado.

¿Qué constituye el conjunto mínimo de #includedeclaraciones?

Esto resulta ser una pregunta no trivial. Una definición TL; DR: un archivo de encabezado debe incluir los archivos de encabezado que definen directamente cada uno de los tipos utilizados directamente o que declaran directamente cada una de las funciones utilizadas en el archivo de encabezado en cuestión, pero no deben incluir nada más. Un puntero o tipo de referencia C ++ no califica como uso directo; Se prefieren las referencias directas.

Hay un lugar para una #includedirectiva gratuita , y esto está en una prueba automatizada. Para cada archivo de encabezado en un paquete de software, genero automáticamente y luego compilo lo siguiente:

#include "path/to/random/header_under_test"
int main () { return 0; }

La compilación debe estar limpia (es decir, libre de advertencias o errores). Las advertencias o errores con respecto a tipos incompletos o tipos desconocidos significan que el archivo de encabezado bajo prueba tiene algunas #includedirectivas faltantes y / o declaraciones de reenvío faltantes. Tenga en cuenta bien: el hecho de que la prueba pase no significa que el conjunto de #includedirectivas sea suficiente, y mucho menos mínimo.

David Hammen
fuente
Entonces, si tengo una biblioteca que define una estructura, llamada A, y esta biblioteca, llamada B, usa esa estructura, y la biblioteca B es usada por el programa C, ¿debo incluir el archivo de encabezado de la biblioteca A en el encabezado principal de la biblioteca B, o debería Acabo de declararlo? la biblioteca A se compila y se vincula con la biblioteca B durante su compilación.
MarcusJ
@MarcusJ - Lo primero que enumeré en Lo que no pertenece en un encabezado fueron declaraciones gratuitas #include. Si el archivo de encabezado B no depende de las definiciones en el archivo de encabezado A, no incluya el archivo de encabezado A en el archivo de encabezado B. Un archivo de encabezado no es el lugar para especificar dependencias de terceros o instrucciones de compilación. Esos van a otro lugar, como un archivo Léame de nivel superior.
David Hammen
1
@MarcusJ: actualicé mi respuesta en un intento de responder a su pregunta. Tenga en cuenta que no hay una respuesta a su pregunta. Ilustraré con un par de extremos. Caso 1: El único lugar donde la biblioteca B usa directamente la funcionalidad de la biblioteca A es en los archivos fuente de la biblioteca B. Caso 2: la biblioteca B es una extensión delgada de la funcionalidad en la biblioteca A, con los archivos de encabezado para la biblioteca B directamente usando tipos y / o funciones definidas en la biblioteca A. En el caso 1, no hay razón para exponer la biblioteca A en los encabezados de la biblioteca B. En el caso 2, esta exposición es bastante obligatoria.
David Hammen
Sí, es el caso 2, lo siento, mi comentario omitió el hecho de que está usando tipos declarados en la biblioteca A en los encabezados de la biblioteca B, estaba pensando que podría reenviar la declaración, pero no creo que eso funcione. Gracias por la actualización.
MarcusJ
¿Agregar constantes a un archivo de encabezado es un gran no-no?
mding5692
15

Además de lo que ya se ha dicho.

Los archivos H siempre deben contener:

  • Documentación del código fuente !!! Como mínimo, cuál es el propósito de los diversos parámetros y valores de retorno de las funciones.
  • Guardias de cabecera, #ifndef MYHEADER_H #define MYHEADER_H ... #endif

Los archivos H nunca deben contener:

  • Cualquier forma de asignación de datos.
  • Definiciones de funciones. Las funciones en línea pueden ser una rara excepción en algunos casos.
  • Cualquier cosa etiquetada static.
  • Typedefs, #defines o constantes que no tienen relevancia para el resto de la aplicación.

(También diría que nunca hay ninguna razón para usar variables globales / externas no constantes, en cualquier lugar, pero esa es una discusión para otra publicación).


fuente
1
Estoy de acuerdo con todo, excepto con lo que has resaltado. Si está creando una biblioteca, sí, debe documentar a los usuarios de su biblioteca. Para un proyecto interno, no debería necesitar abarrotar sus encabezados con documentación, si utiliza buenos nombres de variables y funciones que se explican por sí mismos.
Martiert
55
@martiert También soy de la escuela "deja que el código hable por sí mismo". Sin embargo, al menos siempre debe documentar sus funciones, incluso si nadie más que usted las usará. Las cosas de particular interés son: en caso de que la función tenga manejo de errores, ¿qué códigos de error devuelve y en qué condiciones falla? ¿Qué sucede con los parámetros (buffers, punteros, etc.) si la función falla? Otra cosa que es muy relevante es: ¿los parámetros del puntero devuelven algo a la persona que llama, es decir, esperan memoria asignada? ->
1
Debería ser obvio para la persona que llama qué manejo de errores se realiza dentro de la función y qué no se hace. Si la función espera un búfer asignado, lo más probable es que también deje las verificaciones fuera de los límites a la persona que llama. Si la función se basa en otra función para ejecutarse, esto debe documentarse (es decir, ejecutar link_list_init () antes de link_list_add ()). Y, por último, si la función tiene un "efecto secundario" como la creación de archivos, subprocesos, temporizadores o lo que sea, debe indicarse en la documentación. ->
1
Quizás la "documentación del código fuente" es demasiado amplia aquí, esto realmente pertenece al código fuente. La "documentación de uso" con entrada y salida, condiciones previas y posteriores y efectos secundarios definitivamente deberían ir allí, no en forma épica sino breve .
Seguro el
2
Un poco tardío, pero +1 para la documentación. ¿Por qué existe esta clase? El código no habla por sí mismo. ¿Qué hace esta función? RTFC (lea el archivo .cpp) es un acrónimo obsceno de cuatro letras. Uno nunca debería tener RTFC para entender. El prototipo en el encabezado debe resumir, en algún comentario extraíble (por ejemplo, doxygen), cuáles son los argumentos y qué hace la función. ¿Por qué existe este miembro de datos, qué contiene y es el valor en metros, pies o estadios? Ese también es otro tema para los comentarios (extraíbles).
David Hammen
4

Probablemente nunca diría nunca, pero las declaraciones que generan datos y código a medida que se analizan no deberían estar en un archivo .h.

Las macros, las funciones en línea y las plantillas pueden parecer datos o código, pero no generan código a medida que se analizan, sino que se usan cuando se usan. Estos elementos a menudo deben usarse en más de un .c o .cpp, por lo que pertenecen al .h.

En mi opinión, un archivo de encabezado debe tener la interfaz práctica mínima para un correspondiente .c o .cpp. La interfaz puede incluir #defines, clase, typedef, definiciones de estructura, prototipos de funciones y definiciones externas menos preferidas para variables globales. Sin embargo, si una declaración se usa en un solo archivo fuente, probablemente debería excluirse del .h y estar contenida en el archivo fuente.

Algunos pueden estar en desacuerdo, pero mi criterio personal para los archivos .h es que #incluyen todos los demás archivos .h que necesitan para poder compilar. En algunos casos, esto puede ser una gran cantidad de archivos, por lo que tenemos algunos métodos efectivos para reducir las dependencias externas, como las declaraciones directas a clases que nos permiten usar punteros a objetos de una clase sin incluir lo que podría ser un gran árbol de archivos de inclusión.

DesarrolladorDon
fuente
3

El archivo de encabezado debe tener la siguiente organización:

  • tipo y definiciones constantes
  • declaraciones de objetos externos
  • declaraciones de funciones externas

Los archivos de encabezado nunca deben contener definiciones de objetos, solo definiciones de tipos y declaraciones de objetos.

El d
fuente
¿Qué pasa con las definiciones de funciones en línea?
Kos
Si la función en línea es una función "auxiliar" que solo se usa dentro de un módulo C, entonces colóquela solo en ese archivo .c. Si la función en línea debe ser visible para dos o más módulos, póngala dentro del archivo de encabezado.
elD
Además, si la función tiene que ser visible a través del límite de una biblioteca, no la ponga en línea, ya que obliga a todos los que usan la biblioteca a volver a compilar cada vez que modifica cosas.
Donal Fellows
@DonalFellows: Esa es una solución negativa. Una mejor regla: no coloque cosas en los encabezados que estén sujetas a modificaciones frecuentes. No hay nada de malo en incluir una pequeña función corta en un encabezado si la función no tiene despliegue y tiene una definición clara que solo cambiará si cambia la estructura de datos subyacente. Si la definición de la función cambia porque la definición de la estructura subyacente cambió, sí, debe volver a compilar todo, pero tendrá que hacerlo de todos modos porque la definición de la estructura cambió.
David Hammen
0

Las declaraciones que generan datos y código a medida que se analizan no deben estar en un .harchivo. En lo que respecta a mi punto de vista, un archivo de encabezado solo debe tener la interfaz práctica mínima para un correspondiente .co .cpp.

Ajay Prasad
fuente