Managing header files ********************* .. include:: ../BoutonGoogleTrad.rst Rappel ====== La génération d’un programme (:cpp:`build`) consiste à transformer des codes sources en programme exécutable pour une machine donnée. Ce processus repose sur deux actions : * La **compilation** transforme chaque fichier source (.cpp) en un fichier objet. * La **liaison** (:cpp:`link`) fusionne les différents fichiers objets pour construire le programme exécutable final. La **compilation séparée** (:cpp:`sperarate compilation`) désigne le fait de compiler chaque fichier source (:cpp:`.cpp`) séparément. Ainsi, lors de la compilation d'un fichier source, le compilateur n'a aucune connaissance du contenu présent dans les autres fichiers sources. Cette approche présente plusieurs avantages : * Une meilleure gestion des projets : chaque fichier est dédié à une classe / thématique particulière. * Réduction du temps de compilation par parallélisation. * Améliorer la maintenance et le débogage du code. .. note:: Une compilation est déclenchée pour chaque fichier source et non pour les fichiers d'entête. Organisation ============ Lorsque le compilateur lit une directive :cpp:`#include`, il substitue cette ligne par le contenu du fichier d'en-tête. Le compilateur traite ainsi ces lignes comme si elles appartenaient au fichier source. Si ces nouvelles lignes incluent à leur tour d'autres directives :cpp:`#include` alors le mécanisme de substitution se poursuit. Exporter des fonctions ---------------------- Dans le fichier de code :cpp:`fnt.cpp`, on va trouver l'inclusion du fichier d'en-tête et la définition de la fonction : .. code-block:: cpp // fnt.cpp #include "fnt.h" // header inclusion int fnt(...) { ... } // definition Dans le fichier d'en-tête, on trouve la déclaration de la fonction : .. code-block:: cpp // fnt.h #pragma once int fnt(...); // declaration Ainsi, pour pouvoire utiliser cette fonction dans un autre fichier source, il suffira d'inclure son header. .. note:: La directive :cpp:`#pragma once` sert à éviter que ce fichier d’en-tête soit inclus une deuxième fois par le jeu des inclusions multiples. Exporter une classe ------------------- Dans le fichier d’en-tête, on trouve la définition de la classe T et la déclaration de ses membres : .. code-block:: cpp // file : T.h #pragma once class T { int data; int fnt(...); }; Dans le fichier source, on trouve la définition de chaque fonction membre : .. code-block:: cpp // T.cpp #include "T.h" int T::fnt(...) { .... } L’opérateur :cpp:`::` permet d’indiquer que fnt est une fonction membre de la classe :cpp:`T`. Exporter une fonction template ------------------------------ Il est d'usage de placer toute la définition d'une fonction template dans le fichier d'en-tête : .. code-block:: cpp // template.h #pragma once template T max(T a, T b) { if (b > a) return b; else return a; } En conclusion ------------- * Chaque fichier source doit inclure son fichier d'en-tête. * Chaque fichier d'en-tête doit inclure la directive :cpp:`#pragma once` * Lorsqu'un fichier source ou d'en-tête a besoin d'une fonction ou d'une classe, il doit inclure le fichier d'en-tête associé. * Normalement, aucun fichier :cpp:`.h` ou :cpp:`.cpp` n'inclut un fichier source :cpp:`.cpp`. Les problèmes ============= La directive :cpp:`#pragma once` désactive toute tentative de réinclusion. Cela permet d'optimiser le temps de compilation mais pas uniquement. Nous présentons le problème des inclusions multiples ci-dessous : Inclusion multiple ------------------ Supposons que nous ayons un fichier d'en-tête :cpp:`V2.h` inclus dans deux autres fichiers d'en-tête :cpp:`collision.h` et :cpp:`path.h`. Si un fichier source :cpp:`eleve.cpp`, inclut :cpp:`collision.h` et :cpp:`path.h`, le contenu de :cpp:`V2.h` va être finalement inséré deux fois dans :cpp:`eleve.cpp`. .. image:: double.png :align: center Que risque-t-on ? * Pour les fonctions, le fait d'avoir plusieurs déclarations dans le même fichier source ne pose aucun problème. * Pour les structures et les fonctions templates, le compilateur va émettre une erreur de redéfinition. Heureusement pour nous, la directive :cpp:`#pragma once` règle ce problème car elle va bloquer la réinclusion d'un même fichier d'en-tête et donc éviter les problèmes de redéfinition. .. warning:: Il faut écrire les directives :cpp:`#pragma once` sur la première ligne de chaque fichier d'en-tête. Dépendance cyclique ------------------- .. image:: cycle.png :align: center :scale: 40% Ce scénario arrive lorque * Le fichier d'en-tête :cpp:`A.h` inclut le fichier d'en-tête :cpp:`B.h` * Le fichier d'en-tête :cpp:`B.h` inclut le fichier d'en-tête :cpp:`A.h` En compilant le fichier source :cpp:`A.cpp`, le compilateur va naturellement trouver l'inclusion du fichier d'en-tête :cpp:`A.h` qu'il va copier-coller à l'intérieur du code source. Ensuite, en parcourant les nouvelles lignes issues du fichier d'en-tête :cpp:`A.h`, le compilateur va trouver l'inclusion du fichier d'en-tête :cpp:`B.h` et copier-coller son contenu dans :cpp:`A.cpp`. Mais le contenu du fichier d'en-tête de :cpp:`B.h` incluant le contenu de :cpp:`A.h`, on ne va jamais sortir du cycle ce qui va finir par lever erreur de compilation. .. warning:: Cette situation est rare mais vous pourrez la rencontrer. Il n'y a pas de miracle cette fois, aucune directive magique à l'horizon. Il faudra revoir la conception de votre programme et l'organisation de vos fichiers pour éviter cette situation. Définir une fonction dans un fichier d'en-tête ---------------------------------------------- Nous l'avons déjà dit et il faut absolument l'éviter : définir une fonction dans un fichier d'en-tête. Supposons que par inadvertance, nous ayons malencontreusement défini une fonction :cpp:`test()` dans le fichier fichier d'en-tête :cpp:`test.h`. Lors de la compilation, voici ce qu'il se passe : - Dans le fichier source :cpp:`A.cpp`, cette fonction est définie une seule fois et donne naissance à la fonction :cpp:`void test(void)` dans le fichier :cpp:`A.o`. - Idem dans le fichier :cpp:`B.o`. Seulement, au moment de la liaison, le linker se retrouve avec deux fonctions :cpp:`void test(void)`, une dans *A.o* et l'autre dans *B.o* avec exactement la même définition et il émet une erreur de double définition. .. image:: definheader.jpg :align: center :scale: 40% Comme les pros ============== Voici un schéma qui présente une chaîne de compilation complète. Chaque fichier source (:cpp:`.cpp`) déclenche une compilation. On remarque que ces fichiers source incluent différents fichiers en-tête, mais toujours sans créer de cycle. L'étape de compilation construit des fichiers .o qui correspondent à des morceaux du programme final. Ils seront ensuite liés pour construire un exécutable. Tout projet professionnel inclut des librairies annexes : 3D, IHM, BDD,... Ces librairies ne publient pas leurs codes sources, ceci pour diverses raisons, notamment pour préserver leur propriété intellectuelle. Ainsi, chaque librairie fournit : * Des fichiers en-tête contenant les fonctions/classes exportées par la librairie. * Des fichiers :cpp:`.lib` équivalents aux fichier :cpp:`.o`. Le linker dispose ainsi de toutes les informations nécessaires pour construire l'exécutable final : .. image:: compil.png :align: center :scale: 30% Quizzz ====== .. quiz:: fichierheaders :title: Les fichiers d'entête * :quiz:`{"type":"TF","answer":"F"}` L'étape de liaison se déroule avant l'étape de compilation. * :quiz:`{"type":"TF","answer":"T"}` La compilation séparée permet de compiler les fichiers source indépendamment. * :quiz:`{"type":"TF","answer":"T"}` L'étape de compilation consiste à compiler chaque fichier source. * :quiz:`{"type":"TF","answer":"F"}` On doit définir les fonctions dans les fichiers entête. * :quiz:`{"type":"TF","answer":"F"}` Le compilateur sait résoudre les dépendances cycliques. * :quiz:`{"type":"TF","answer":"T"}` La directive :cpp:`#pragma once` se rencontre dans les fichiers d'entête. * :quiz:`{"type":"TF","answer":"F"}` Si l'on inclut plusieurs fois le même fichier d'en-tête dans un même fichier source, cela déclenche une erreur de liaison. * :quiz:`{"type":"TF","answer":"T"}` Une librairie externe, si elle ne fournit pas de fichiers source, propose un fichier précompilé :cpp:`.lib` à la place ? * :quiz:`{"type":"TF","answer":"T"}` Il est d'usage qu'un fichier source inclut son propre fichier d'entête.