Cuando se crea una struct
que contiene
funciones miembro, se está creando un nuevo tipo de dato. En
general, se intenta que ese tipo sea fácilmente accesible. En
resumen, se quiere que la interfaz (la declaración) esté separada
de la implmentación (la definición de los métodos) de
modo que la implementación pueda cambiar sin obligar a recompilar
el sistema completo. Eso se consigue poniendo la declaración
del nuevo tipo en un fichero de cabecera.
Cuando yo aprendí a programar en C, el fichero de cabecera era
un misterio para mi. Muchos libros de C no hacen hincapié, y el
compilador no obliga a hacer la declaración de las funciones,
así que parecía algo opcional la mayor parte de las veces,
excepto cuando se declaraban estrucutras. En C++ el uso de los
ficheros de cabecera se vuelve claro como el cristal. Son
prácticamente obligatorios para el desarrollo de programas
sencillos, y en ellos podrá información muy específica:
declaraciones. El fichero de cabecera informa al compilador de
lo que hay disponible en la librería. Puede usar la librería
incluso si sólo se dispone del fichero de cabecera y el fichero
objeto o el fichero de librería; no necesita disponer del código
fuente del fichero cpp
. En el fichero de
cabecera es donde se guarda la especificación de la interfaz.
Aunque el compilador no lo obliga, el mejor modo de construir
grandes proyectos en C es usar librerías; colecciones de
funciones asociadas en un mismo módulo objeto o librería, y usar
un fichero de cabecera para colocar todas las declaraciones de
las funciones. Es de rigor en C++, Podría
meter cualquier función en una librería C, pero el tipo
abstracto de dato C++ determina las funciones que están
asociadas por medio del acceso común a los datos de una
struct
. Cualquier función miembro debe ser declarada en
la declaración de la struct
; no puede ponerse en otro
lugar. El uso de librerías de funciones fue fomentado en C y
institucionalizado en C++.
Cuando se usa función de una librería, C le permite la
posibilidad de ignorar el fichero de cabecera y simplemente
declarar la función a mano. En el pasado, la gente hacía eso a
veces para acelerar un poquito la compilación evitando la
tarea de abrir e incluir el fichero (eso no supone ventaja
alguna con los compiladores modernos). Por ejemplo, la
siguiente es una declaración extremadamente vaga de la función
printf()
(de
<stdio.h>
):
printf(...);
Estos puntos suspensivos [49] especifican una lista de argumentos
variable
[50],
que dice: la printf()
tiene algunos
argumentos, cada uno con su tipo, pero no se sabe
cuales. Simplemente, coge los argumentos que veas y
aceptalos. Usando este tipo de declaración, se suspenden todas
las comprobaciones de errores en los argumentos.
Esta práctica puede causar problemas sutiles. Si declara funciones «a mano», en un fichero puede cometer un error. Dado que el compilador sólo verá las declaraciones hechas a mano en ese fichero, se adaptará al error. El programa enlazará correctamente, pero el uso de la función en ese fichero será defectuoso. Se trata de un error difícil de encontrar, y que se puede evitar fácilmente usando el fichero de cabecera correspondiente.
Si se colocan todas las declaraciones de funciones en un fichero de cabecera, y se incluye ese fichero allí donde se use la función se asegurará una declaración consistente a través del sistema completo. También se asegurará de que la declaración y la definición corresponden incluyendo el fichero de cabecera en el fichero de definición.
Si declara una struct
en un fichero de cabecera en
C++, debe incluir ese fichero allí donde
se use una struct
y también donde se definan los
métodos de la struct
. El compilador de C++ devolverá
un mensaje de error si intenta llamar a una función, o llamar
o definir un método, sin declararla primero. Imponiendo el uso
apropiado de los ficheros de cabecera, el lenguaje asegura la
consistencia de las librerías, y reduce el número de error
forzando que se use la misma interface en todas partes.
El fichero de cabecera es un contrato entre el programador de
la librería y el que la usa. El contrato describe las
estructuras de datos, expone los argumentos y valores de
retorno para las funciones. Dice, «Esto es lo que hace
mi librería». El usuario necesita parte de esta
información para desarrollar la aplicación, y el compilador
necesita toda ella para generar el código correcto. El usuario
de la struct
simplemente incluye el fichero de
cabecera, crea objetos (instancias) de esa struct
, y
enlaza con el módulo objeto o librería (es decir, el código
compilado)
El compilador impone el contrato obligando a declarar todas las estruturas y funciones antes que puedan ser usadas y, en el caso de métodos, antes de ser definidos. De ese modo, se le obliga a poner las declaraciones en el fichero de cabecera e incluirlo en el fichero en el que se definen los métodos y en los ficheros en los que se usen. Como se incluye un único fichero que describe la librería para todo el sistema, el compilador puede asegurar la consistencia y evitar errores.
Hay ciertos asuntos a los que debe prestar atención para organizar su código apropiadamente y escribir ficheros de cabecera eficaces. La regla básica es «únicamente declaraciones», es decir, sólo información para el compiladore pero nada que requiera alojamiento en memoria ya sea generando código o creando variables. Esto es así porque el fichero de cabecera normalmente se incluye en varias unidades de traducción en un mismo proyecto, y si el almacenamiento para un identificador se pide en más de un sitio, el enlazador indicará un error de definición múltiple (ésta es la regla de definición única de C++: Se puede declarar tantas veces como se quiera, pero sólo puede haber una definición real para cada cosa).
Esta norma no es completamente estricta. Si se define una variable que es «file static» (que tiene visibilidad sólo en un fichero) dentro de un fichero de cabecera, habrá múltiples instancias de ese dato a lo largo del proyecto, pero no causará un colisión en el enlazador [51]. Básicamente, debe evitar cualquier cosa en los ficheros de cabecera que pueda causar una ambigüedad en tiempo de enlazado.
[49] (N. de T. ellipsis) en inglés)
[50] Para escribir una definición de función que toma una lista de argumentos realmente variable, debe usar varargs, aunque se debería evitar en C++. Puede encontar información detallada sobre el uso de varargs en un manual de C.
[51] Sin embargo, en C++ estándar «file static» es una característica obsoleta.