Fichiers multiples

Lorsqu’un programme devient volumineux, il est peu rentable de le placer dans un seul fichier : la compilation devient très longue, puisqu’il faut tout recompiler chaque fois, et il est difficile de s’y retrouver.

Pour simplifier alors le travail, on répartit les différentes routines dans plusieurs fichiers source (*.cpp). Chacun de ces fichiers peut alors être compilé indépendamment, produisant un fichier objet (*.obj). L’ensemble des fichiers objets est ensuite regroupé par l’éditeur de liens pour former un programme exécutable unique (*.exe, sur PC).

Dans la pratique, les différents fichiers doivent d’abord répondre à une certaine logique. Par exemple, on place fréquemment dans un fichier séparé une classe entièrement implémentée, ou deux ou trois si elles sont reliées. Il ne faut surtout pas répartir au hasard les fonctions dans plusieurs fichiers, car il serait vite très difficile de s’y retrouver.

Comme les fichiers doivent communiquer entre eux, il faut au moins un fichier en-tête qui leur soit commun. En pratique, on utilise même souvent un fichier en-tête pour chaque source, sauf celui qui contient main ; en général on donne à ce fichier le même nom avec le suffixe *.h (mais ce n’est nullement obligatoire). Lorsqu’on utilise cette méthode très fréquente, le fichier en-tête peut être considéré comme l’interface d’un module dont le fichier source est l’implantation. Dans ce fichier d’en-tête on placera toutes les déclarations (de classes, de fonctions, de constantes, de variables, etc.) susceptibles d’être utilisées par les autres. On n’oubliera pas les fonctions en ligne, car le compilateur doit les connaître entièrement pour les placer directement dans le code produit.

Dans le fichier source proprement dit, on trouvera généralement une directive d’inclusion du fichier en-tête correspondant, suivie éventuellement d’autres directives d’inclusion, soit pour les librairies standard, soit pour les autres fichiers en-têtes du même programme utilisés par le fichier courant. On trouvera ensuite l’implantation des fonctions qui ne sont pas en ligne.

Dans le fichier contenant main, on trouvera toutes les inclusions d’en-têtes nécessaires, suivies par main et éventuellement quelques fonctions étroitement liées à elle.

Prenons un exemple simple (et artificiel). Imaginons un programme faisant des calculs sur des matrices de fractions, implantées par une classe spéciale du même genre que la classe liste, mais adaptée aux fractions. Un tel programme pourrait être réparti dans cinq modules différents :

En réalité, les quatre premiers modules sont répartis chacun en un en-tête et un source. Voici l’allure qu’ils pourraient avoir :

fraction.h

#ifndef _FRACTION_H#define _FRACTION_Hclass fraction {     // ....définition de la classe      // avec fonctions en ligne éventuelles     };				#endif

fraction.cpp

#include "fraction.h"				// ici implantation des fonctions membres de // la classe fraction qui ne sont pas en ligne.

listefra.h

#ifndef _LISTEFRA_H#define _LISTEFRA_H				#include "fraction.h"class noeud_fra;    // définition  dans listefra.cpp				class liste_fra {     // listes de fractions     };				#endif

listefra.cpp

#include "fraction.h"				class noeud_fra {     // ...classe utilisée seulement par liste_fra     };// implantations des fonctions de noeud_fra  et// liste_fra qui ne sont pas en ligne

matrfra.h

#ifndef _MATRFRA_H#define _MATRFRA_H				#include "fraction.h"class matrice_fra {     // ...     };				#endif

matrfra.cpp

#include "listefra.h"#include "matrfra.h"				// implantations des fonctions membres de matrice_fra// qui ne sont pas en ligne

iosfract.h

#ifndef _IOSFRACT_H#define _IOSFRACT_H				#include "matrfra.h"// déclarations de routines d’affichage et de lecture// de fractions et de matrices				#endif

iosfract.cpp

#include <iostream.h>#include "iosfract.h"				// implantations des routines définies dans iosfract.h

mainfra.cpp

#include "iosfract.h"				main(){     // ...}

 

Voilà notre programme bien silhouetté. On notera que chaque fichier n’inclut que les en-têtes dont il a besoin. Par exemple, les listes de fractions ne sont utilisées que par l’implantation des matrices ; elles n’apparaissent donc que dans matrfra.cpp, et non dans matrfra.h ni dans les autres. De même, les flots d’entrées-sorties déclarés dans <iostream.h> ne sont employés que par l’implantation des fonctions d’entrées-sorties des fractions et matrices.

On remarquera aussi une écriture classique dans les fichiers en-têtes, avec la clause #ifndef _XXX_H suivie par un #define _XXX_H. Ceci permet de n’inclure qu’une seule fois un même fichier d’en-tête dans un fichier. Cela peut paraître inutile, mais remarquez que listefra.h et matrfra.h incluent tous deux fraction.h, et sont tous deux inclus dans matrfra.cpp. Dès lors, si l’on oublie d’utiliser cette clause conditionnelle, c sera inclus deux fois dans c, ce qui non seulement augmentera le temps de compilation mais aussi risque de provoquer une erreur car le compilateur n’acceptera pas que l’on définisse deux fois certains éléments du fichier (variables globales, etc.).

On peut à présent compiler chacun de ces fichiers, soit séparément, soit ensemble (avec un projet, voir ci-après). On obtient alors sur disque cinq fichiers objets fraction.obj, ..., mainfra.obj. Reste à les relier entre eux, ainsi qu’à la librairie standard, pour obtenir le programme souhaité. Cela est fait par l’intermédiaire d’un projet, que nous allons examiner à présent.

Précédent Précédent Sommaire Sommaire Suivant Suivant