Modularité, compilation séparée#

Rappelez-vous notre livre de recettes. À l’époque, nous avons vu comment découper un programme en fonctions pour plus de modularité. Cela permet de mieux le comprendre, petit bout par petit bout, d’éviter les redites, etc.

Nous allons de même découper un programme en plusieurs fichiers.

Compilation séparée#

Exemple#

Considérons les trois programmes suivants :

Dans Jupyter :

int monMax(int a, int b) {
    if ( a >= b )
        return a;
    else
        return b;
}
monMax(10, 1)

programme1.cpp : maximum de deux entiers, avec un exemple

#include <iostream>
using namespace std;

int monMax(int a, int b) {
    if ( a >= b )
        return a;
    else
        return b;
}

int main() {
    cout << monMax(1, 3) << endl;
    return 0;
}

programme2.cpp : maximum de deux entiers, avec interactivité

#include <iostream>
using namespace std;

int monMax(int a, int b) {
    if ( a >= b )
        return a;
    else
        return b;
}

int main() {
    cout << "Entrez a et b:" << endl;
    int a, b;
    cin >> a >> b;
    cout << "Le maximum est: "
         << monMax(a, b) << endl;
    return 0;
}

On constate une répétition : les trois programmes définissent exactement la même fonction monMax, qu’ils utilisent ensuite différemment.

Pourrait-on partager la fonction monMax entre ces trois programmes ?

C’est ce que nous allons faire en définissant une mini-bibliothèque. Voyons à quoi cela ressemble.

Exemple : une bibliothèque max simpliste#

Contenu du fichier max_simpliste.hpp :

/** La fonction max
 *  @param x, y deux entiers
 *  @return un entier,
 *  le maximum de x et de y
 **/
int monMax(int a, int b) {
    if ( a >= b )
        return a;
    else
        return b;
}

Pour utiliser cette bibliothèque, il suffit de l” inclure (include directive) :

#include "max_simpliste.hpp"
monMax(1, 3)

Attention

On appelle cela une bibliothèque en entêtes seuls (header only).

En C++, il y a des cas d’usage où cela peut être pertinent.

Il y a de sérieuses limitations à cette façon de structurer une bibliothèque.

Dans ce cours on évitera.

Exemple : une bibliothèque max dans les règles#

Contenu du fichier max.hpp :

/** La fonction max
 *  @param x, y deux entiers
 *  @return un entier,
 *  le maximum de x et de y
 **/
int monMax(int a, int b);

Contenu du fichier max.cpp :

#include "max.hpp"

int monMax(int a, int b) {
    if ( a >= b )
        return a;
    else
        return b;
}

Exemple : deux programmes utilisant la bibliothèque max#

Contenu du fichier programme1.cpp:

#include <iostream>
using namespace std;
#include "max.hpp"

int main() {
    cout << monMax(1, 3) << endl;
    return 0;
}

Contenu du fichier programme2.cpp:

#include <iostream>
using namespace std;
#include "max.hpp"

int main() {
    cout << "Entrez a et b :" << endl;
    int a, b;
    cin >> a >> b;
    cout << "Le maximum est : "
         << monMax(a, b) << endl;
    return 0;
}

Exemple : les tests de la bibliothèque max#

Contenu du fichier max-test.cpp :

#include <iostream>
using namespace std;

#include "max.hpp"

/** Infrastructure minimale de test **/
#define CHECK(test) if (!(test)) cerr << "Test failed in file " << __FILE__ << " line " << __LINE__ << ": " #test << endl

void monMaxTest() {
    CHECK( monMax(2,3) == 3 );
    CHECK( monMax(5,2) == 5 );
    CHECK( monMax(1,1) == 1 );
}

int main() {
    monMaxTest();
}

Qu’avons-nous vu ?#

Déclaration de fonctions#

Syntaxe

int monMax(int a, int b);

Sémantique

  • Le programme définit (function definition) quelque part une fonction monMax avec cette signature (function signature) :
    type des paramètres et type du résultat

  • Cette définition n’est pas forcément dans le même fichier

  • Si cette définition n’existe pas ou n’est pas unique, une erreur est déclenchée par le compilateur

  • Cette erreur est déclenchée au moment où l’on combine les différents fichiers : voir plus loin «Édition de liens»

Indication

♣ Application Deux fonctions qui s’appellent réciproquement

Compilation séparée (1)#

  • Un programme peut être composé de plusieurs fichiers source (source file)
    Extension : .cpp
    Contenu :

    • Des définitions de fonctions

    • Des variables globales, …

  • Chaque fichier source est compilé en un fichier objet (object file)
    Extension : .o
    Contenu :

    • Le code binaire des fonctions, …

  • L” éditeur de liens (linker) combine plusieurs fichiers objet en un fichier exécutable (executable file).

Voyons cela pour un programme voulant utiliser la bibliothèque max :

Les sources sont max.cpp et programme.cpp.

On les compile séparément avec :

clang++ -c max.cpp
clang++ -c programme.cpp

Cela produit les fichiers objets max.o et programme.o. Chacun est un bout incomplet de programmes binaires : max.o contient le code binaire de la fonction max mais pas la fonction main, et réciproquement pour programme.o.

Il ne reste plus qu’à combiner ces deux bouts de programmes binaires pour obtenir un programme complet.

clang++ programme.o max.o -o programme

Maintenant, on peut exécuter le programme obtenu autant de fois qu’on le souhaite :

./programme

Compilation séparée (2)#

Au moment de l’édition de lien :

  • Chaque fonction utilisée doit être définie une et une seule fois

  • La fonction main doit être définie une et une seule fois

Indication

♣ Quelques variantes autour des fichiers objets

  • Bibliothèques (.a) : Une archive contenant plusieurs fichiers objets .o

  • Bibliothèques dynamiques (.so) : Édition de lien dynamique au lancement du programme

Fichiers d’entête#

Définition : Fichier d’entête

Fichier .hpp (ou .h en C) contenant la déclaration des fonctions définies dans le fichier .cpp correspondant

Exemple : Fichier d’entête max.hpp

int monMax(int a, int b);

Utilisation d’un fichier d’entête#

Syntaxe

#include "max.hpp"

Sémantique

Utiliser la bibliothèque max

Indication

Implantation en C++

  • Équivalent à copier-coller le contenu de max.hpp à l’emplacement du #include "max.hpp"

  • ♣ Géré par le préprocesseur (cpp)

Inclusion de fichiers d’entêtes standards#

Syntaxe

#include <iostream>

Sémantique

  • Charge la déclaration de toutes les fonctions définies dans la bibliothèque standard iostream de C++

  • Le fichier iostream est recherché dans les répertoires standards du système

  • Sous linux : /usr/include, …

Résumé#

Résumé : implantation d’une bibliothèque en C++#

Écrire un fichier d’entête (max.hpp)

  • La déclaration de toutes les fonctions publiques

  • Avec leur documentation !

Écrire un fichier source (max.cpp)

  • La définition de toutes les fonctions

  • Inclure le fichier .hpp !

Écrire un fichier de tests (maxTest.cpp)

  • Les fonctions de tests

  • Une fonction main lançant tous les tests

Résumé : utilisation d’une bibliothèque en C++#

Inclusion des entêtes

#include <iostream>   // fichier d'entête standard
#include "max.hpp"    // fichier d'entête perso

Compilation

clang++ -c max.cpp
clang++ -c programme1.cpp
clang++ max.o programme1.o -o programme1

En une seule étape

clang++ max.cpp programme1.cpp -o programme1

Suite#

Après cette discussion des notions de modularité et de compilation séparée, passons à quelques digressions sur la surcharge, les templates et les espaces de noms.