4.7. Convenciones 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 para uno mismo y para los demás. Además, se quiere que la interfaz (la declaración) esté separada de la implementació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 mí. Muchos libros de C no parecen hacer hincapié en ello, 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 estructuras. En C++ el uso de los ficheros de cabecera se vuelve claro como el cristal. Son prácticamente obligatorios para desarrollar programas con facilidad, y en ellos pondrá 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 una 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 [52] especifican una lista de argumentos variable [53], que dice: printf() tiene algunos argumentos, cada uno con su tipo, pero no hagas caso de eso. Simplemente, coge los argumentos que veas y acéptalos. 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, puede que se adapte 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 y donde se defina la función, se asegurará una declaración coherente 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 normal, o llamar o definir un método, sin declararla primero. Imponiendo el uso apropiado de los ficheros de cabecera, el lenguaje asegura la coherencia de las librerías, y reduce el número de errores forzando que se use la misma interfaz 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 estructuras y funciones antes de 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 coherencia 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 primera cuestión se refiere a lo que se puede poner en los ficheros de cabecera. La regla básica es «únicamente declaraciones», es decir, sólo información para el compilador 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 pueden declarar las cosas 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á una colisión en el enlazador [54]. Básicamente, debe evitar cualquier cosa en los ficheros de cabecera que pueda causar una ambigüedad en tiempo de enlazado.



[52] N. de T.: ellipsis en inglés.

[53] Para escribir una definición de función que toma una lista de argumentos realmente variable, debe usar varargs, aunque se deberían evitar en C++. Puede encontrar información detallada sobre el uso de varargs en un manual de C.

[54] Sin embargo, en C++ estándar «file static» es una característica obsoleta.