Skip to content
Snippets Groups Projects
jupytext:
  text_representation:
    extension: .md
    format_name: myst
    format_version: 0.13
kernelspec:
  display_name: C++17
  language: C++17
  name: xcpp17
rise:
  auto_select: first
  autolaunch: false
  centered: false
  controls: false
  enable_chalkboard: true
  height: 100%
  margin: 0
  maxScale: 1
  minScale: 1
  scroll: true
  slideNumber: true
  start_slideshow_at: selected
  transition: none
  width: 90%

+++ {"slideshow": {"slide_type": "slide"}}

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.

+++ {"slideshow": {"slide_type": "slide"}}

Compilation séparée

+++ {"slideshow": {"slide_type": "fragment"}}

Exemple

Considérons les trois programmes suivants :

+++ {"slideshow": {"slide_type": "fragment"}, "tags": []}

Dans Jupyter :

:tags: []

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

+++ {"slideshow": {"slide_type": "slide"}, "tags": []}

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;
}

+++ {"slideshow": {"slide_type": "slide"}, "tags": []}

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;
}

+++ {"slideshow": {"slide_type": "slide"}}

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.

+++ {"slideshow": {"slide_type": "slide"}}

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;
}

+++ {"slideshow": {"slide_type": "fragment"}}

Pour utiliser cette bibliothèque, il suffit de l'inclure :

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

+++ {"slideshow": {"slide_type": "fragment"}}

:::{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. :::

+++ {"slideshow": {"slide_type": "slide"}}

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

+++ {"slideshow": {"slide_type": "fragment"}}

% TODO: ici et dans les pages suivantes: use literalinclude

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);

+++ {"slideshow": {"slide_type": "fragment"}}

Contenu du fichier max.cpp :

#include "max.hpp"

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

+++ {"slideshow": {"slide_type": "slide"}}

Exemple : deux programmes utilisant la bibliothèque max

+++ {"slideshow": {"slide_type": "fragment"}}

Contenu du fichier programme1.cpp:

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

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

+++ {"slideshow": {"slide_type": "fragment"}}

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;
}

+++ {"slideshow": {"slide_type": "slide"}}

Exemple : les tests de la bibliothèque max

+++ {"slideshow": {"slide_type": "fragment"}}

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();
}

+++ {"slideshow": {"slide_type": "slide"}}

Qu'avons-nous vu ?

+++ {"slideshow": {"slide_type": "fragment"}}

Déclaration de fonctions

+++ {"slideshow": {"slide_type": "fragment"}}

:::{admonition} Syntaxe

int monMax(int a, int b);

:::

+++ {"slideshow": {"slide_type": "fragment"}}

:::{admonition} Sémantique

  • Le programme définit quelque part une fonction monMax avec cette 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»

+++ {"slideshow": {"slide_type": "fragment"}}

:::{hint} :clubs: Application Deux fonctions qui s'appellent réciproquement :::

+++ {"slideshow": {"slide_type": "slide"}}

Compilation séparée (1)

  • Un programme peut être composé de plusieurs fichiers source
    Contenu :
    • Des définitions de fonctions
    • Des variables globales, ...

+++ {"slideshow": {"slide_type": "fragment"}, "tags": []}

  • Chaque fichier source est compilé en un fichier objet (extension : .o)
    Contenu :
    • Le code binaire des fonctions, ...

+++ {"slideshow": {"slide_type": "fragment"}, "tags": []}

  • L'éditeur de liens combine plusieurs fichiers objet en un fichier exécutable

+++ {"slideshow": {"slide_type": "fragment"}}

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

Les sources sont max.cpp et programme.cpp.

+++ {"slideshow": {"slide_type": "fragment"}}

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.

+++ {"slideshow": {"slide_type": "fragment"}}

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

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

+++ {"slideshow": {"slide_type": "fragment"}}

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

./programme

+++ {"slideshow": {"slide_type": "slide"}}

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

+++ {"slideshow": {"slide_type": "fragment"}}

:::{hint} :clubs: 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 :::

+++ {"slideshow": {"slide_type": "slide"}}

Fichiers d'entête

:::{prf:definition} Fichier d'entête Fichier .hpp (ou .h en C) contenant la déclaration des fonctions définies dans le fichier .cpp correspondant :::

+++ {"slideshow": {"slide_type": "fragment"}}

:::{prf:example} Fichier d'entête max.hpp

int monMax(int a, int b);

:::

+++ {"slideshow": {"slide_type": "fragment"}}

Utilisation d'un fichier d'entête

:::{admonition} Syntaxe

#include "max.hpp"

::: :::{admonition} Sémantique Utiliser la bibliothèque max :::

+++ {"slideshow": {"slide_type": "fragment"}}

:::{hint} Implantation en C++

  • Équivalent à copier-coller le contenu de max.hpp à l'emplacement du #include "max.hpp"
  • :clubs: Géré par le préprocesseur (cpp)

+++ {"slideshow": {"slide_type": "slide"}}

#### Inclusion de fichiers d'entêtes standards

:::{admonition} Syntaxe
``` c++
#include <iostream>

:::

:::{admonition} 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, ... :::

+++ {"slideshow": {"slide_type": "skip"}, "tags": []}

Résumé

+++ {"slideshow": {"slide_type": "slide"}, "tags": []}

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

+++ {"slideshow": {"slide_type": "fragment"}}

:::{admonition} Écrire un fichier d'entête (max.hpp)

  • La déclaration de toutes les fonctions publiques
  • Avec leur documentation ! :::

+++ {"slideshow": {"slide_type": "fragment"}}

:::{admonition} Écrire un fichier source (max.cpp)

  • La définition de toutes les fonctions
  • Inclure le fichier .hpp ! :::

+++ {"slideshow": {"slide_type": "fragment"}}

:::{admonition} Écrire un fichier de tests (maxTest.cpp)

  • Les fonctions de tests
  • Une fonction main lançant tous les tests :::

+++ {"slideshow": {"slide_type": "slide"}}

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

:::{admonition} Inclusion des entêtes

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

:::

+++ {"slideshow": {"slide_type": "fragment"}}

:::{admonition} Compilation

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

:::

+++ {"slideshow": {"slide_type": "fragment"}}

:::{admonition} En une seule étape

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

:::

+++ {"slideshow": {"slide_type": "skip"}, "tags": []}

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.