4.7. Conveciones para los ficheros de cabecera

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

4.7.1. Importancia de los ficheros de cabecera

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.