Programación modular

Compilación independiente

En las primeras prácticas creábamos un único fichero fuente (.cpp) que contenía todo nuestro programa. Sin embargo, si nuestro programa aumenta de tamaño es mejor dividirlo en varias unidades de compilación, de forma que cada una de ellas se pueda compilar por separado y no sea necesario compilar todo el programa para cada modificación que realicemos. El criterio para dividir un programa debe ser el seguir la abstracción por módulos.

Para poder dividir un programa extenso en varias unidades de compilación será necesario usar ficheros de cabecera. Estos ficheros de cabecera incluirán, entre otras cosas, declaraciones de datos y prototipos de funciones.

Normalmente, cada unidad de programa (fichero fuente .cpp) tendrá asociado un fichero de cabecera (.h) que contendrá los prototipos de las funciones que son invocadas desde otras unidades de programa (otros ficheros fuente). Es decir, aquellas funciones de una unidad de programa que no sean requeridas por funciones externas a esa unidad, tendrán su prototipo al comienzo de la unidad de programa (fichero fuente .cpp) y no en el fichero de cabecera asociado.

También es conveniente recordar que un programa en C++ tiene una única función principal (main()), por lo que ésta estará definida en sólo uno de esos ficheros fuente.

Para que desde una unidad de programa se pueda invocar a una función que esta definida en otra unidad, será necesario incluir el fichero de cabecera que contiene el prototipo de dicha función. ¿Pero que ocurre cuando enlazamos (link) un programa compuesto de varias unidades, y en algunas de ellas se ha incluido el mismo fichero de cabecera? Lo que ocurre es que el enlazador (linker) se puede encontrar con varios prototipos de la misma función y/o varias definiciones de los mismos tipos de datos, generando sus errores respectivos. Para evitar esto se puede emplear la directiva de compilación condicional (#ifndef ... #endif) cuya sintaxis es:
#ifndef CONSTANTE
 #define CONSTANTE
// Definición de tipos y prototipos de funciones
#endif
donde CONSTANTE es el identificador al que se le asocia el conjunto de definiciones de tipos y prototipos de funciones definidas a continuación.

A grandes rasgos, la directiva de compilación condicional funcionaría de la siguiente forma: si en el enlace que lleva realizado el compilador no esta definida esa CONSTANTE entonces incluye, en dicho linkaje, ese conjunto de definiciones y prototipos, en caso contrario, es decir si ya está definida no la vuelve a incluir, y de esa forma no existirán varios prototipos de la misma función o varias definiciones de los mismos datos.

La unión de todas estas unidades de programa constituye un proyecto. Recordar que en C++Builder solo se debe incluir al proyecto los ficheros fuentes (.cpp) y NO los ficheros de cabecera.
Ejemplo
Supongamos que se desea crear un módulo que soporte operaciones sobre números complejos. Se creará el archivo de cabecera complejos.h y el archivo de implementación complejos.cpp.

/*

    complejos.h

    Módulo de soporte de números complejos
    Archivo de cabecera

*/

#ifndef _MODULO_COMPLEJOS_H_
#define _MODULO_COMPLEJOS_H_

struct NumeroComplejo {
    int a;
    int b;
};

void imprimeNumeroComplejo(const NumeroComplejo &n);

#endif


Lo anterior es el archivo de cabecera, complejos.h. Al definir la constante _MODULO_COMPLEJOS_H_, si se incluye en más de un archivo de implementación, no se compilará el contenido de la cabecera, es decir, se asegura que sóo se compila una vez nada más (la primera).
Ahora, sólo será necesario crear el archivo de implementación, donde se hagan las definiciones pendientes (en este caso, sólo queda por definir la función imprimeNumeroComplejo()).

/*

    complejos.cpp

    Módulo de soporte de números complejos
    Archivo de implementación

*/

#include "complejos.h"

void imprimeNumeroComplejo(const NumeroComplejo &n)
{
cout << n.a << " + i" << b << endl;
}

Finalmente, necesitaremos un programa principal que haga uso del módulo. Por ejemplo:

// ppal.cpp
#include <iostream>
#include "complejos.h"
using namespace std;
int main()
{
    NumeroComplejo n;
    n.a = 5; n.b = 2;
    imprimeNumeroComplejo( n );
}

Como es lógico, el programa principal debe incluir la cabecera del módulo de números complejos para poder utilizarlo. Además, ambos archivos de implementación (complejos.cpp y ppal.cpp) deben compilarse juntos, para que el código de la función imprimeNumeroComplejo() esté disponible. Una representación gráfica de las dependencias sería más o menos el siguiente:

Dependencias entre módulos