Poly Cpp

Document Sample
Poly Cpp Powered By Docstoc
					      e
Facult´ des Sciences de Luminy                                                                                    e
                                                                                                                 D´partement d’Informatique




                                           Le langage C++
                                                Henri Garreta


              e
Table des mati`res
     e         e
1 El´ments pr´alables                                                                                                                                                    3
                       e
  1.1 Placement des d´clarations de variables . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   3
            e
  1.2 Bool´ens . . . . . . . . . . . . . . . . . . . . . .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   3
         ee
  1.3 R´f´rences . . . . . . . . . . . . . . . . . . . . .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   4
       1.3.1 Notion . . . . . . . . . . . . . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   4
               ee               e
       1.3.2 R´f´rences param`tres des fonctions . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   4
                                          ee
       1.3.3 Fonctions renvoyant une r´f´rence . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   4
               ee                       e
       1.3.4 R´f´rences sur des donn´es constantes .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   5
               ee
       1.3.5 R´f´rences ou pointeurs ? . . . . . . . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   5
  1.4 Fonctions en ligne . . . . . . . . . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   5
                    e
  1.5 Valeurs par d´faut des arguments des fonctions         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   6
  1.6 Surcharge des noms de fonctions . . . . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   6
                 e                       e
  1.7 Appel et d´finition de fonctions ´crites en C . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   7
            e
  1.8 Entr´es-sorties simples . . . . . . . . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   7
                                   e
  1.9 Allocation dynamique de m´moire . . . . . . .          .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   8

2 Classes                                                                                                                                                                 9
  2.1 Classes et objets . . . . . . . . . . . . . . . . . . . . .            . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    9
          e
  2.2 Acc`s aux membres . . . . . . . . . . . . . . . . . . . .              . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   10
                 e
      2.2.1 Acc`s aux membres d’un objet . . . . . . . . .                   . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   10
                 e a                              e a
      2.2.2 Acc`s ` ses propres membres, acc`s ` soi-mˆme    e               . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   10
                                        e
      2.2.3 Membres publics et priv´s . . . . . . . . . . . .                . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   11
      2.2.4 Encapsulation au niveau de la classe . . . . . .                 . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   11
      2.2.5 Structures . . . . . . . . . . . . . . . . . . . . .             . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   12
        e
  2.3 D´finition des classes . . . . . . . . . . . . . . . . . . .            . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   12
               e         e e           e             e
      2.3.1 D´finition s´par´e et op´rateur de r´solution de                  port´e
                                                                                  e          .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   12
                            e                     e
      2.3.2 Fichier d’en-tˆte et fichier d’impl´mentation . .                 . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   13
  2.4 Constructeurs . . . . . . . . . . . . . . . . . . . . . . .            . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   14
               e
      2.4.1 D´finition de constructeurs . . . . . . . . . . .                 . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   14
      2.4.2 Appel des constructeurs . . . . . . . . . . . . .                . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   16
                                  e
      2.4.3 Constructeur par d´faut . . . . . . . . . . . . .                . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   17
      2.4.4 Constructeur par copie (clonage) . . . . . . . .                 . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   17
  2.5 Construction des objets membres . . . . . . . . . . . .                . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   18
  2.6 Destructeurs . . . . . . . . . . . . . . . . . . . . . . .             . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   19
  2.7 Membres constants . . . . . . . . . . . . . . . . . . . .              . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   20
                   e
      2.7.1 Donn´es membres constantes . . . . . . . . . .                   . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   20
      2.7.2 Fonctions membres constantes . . . . . . . . .                   . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   21
  2.8 Membres statiques . . . . . . . . . . . . . . . . . . . .              . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   22
                   e
      2.8.1 Donn´es membres statiques . . . . . . . . . . .                  . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   22
      2.8.2 Fonctions membres statiques . . . . . . . . . .                  . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   23
  2.9 Amis . . . . . . . . . . . . . . . . . . . . . . . . . . . .           . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   23
      2.9.1 Fonctions amies . . . . . . . . . . . . . . . . . .              . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   23
      2.9.2 Classes amies . . . . . . . . . . . . . . . . . . .              . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   25



                                                        1
                    e
3 Surcharge des op´rateurs                                                                                                                                        26
  3.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .              .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   26
                                 e
      3.1.1 Surcharge d’un op´rateur par une fonction membre . . .                        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   26
                                 e
      3.1.2 Surcharge d’un op´rateur par une fonction non membre                          .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   27
  3.2 Quelques exemples . . . . . . . . . . . . . . . . . . . . . . . . .                 .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   28
                                               e
      3.2.1 Injection et extraction de donn´es dans les flux . . . . .                     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   28
      3.2.2 Affectation . . . . . . . . . . . . . . . . . . . . . . . . .                  .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   29
         e
  3.3 Op´rateurs de conversion . . . . . . . . . . . . . . . . . . . . .                  .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   31
      3.3.1 Conversion vers un type classe . . . . . . . . . . . . . .                    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   31
      3.3.2 Conversion d’un objet vers un type primitif . . . . . . .                     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   31

    e
4 H´ritage                                                                                                                                                        32
                                   e e
  4.1 Classes de base et classes d´riv´es . . . . . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   32
        e                     e
  4.2 H´ritage et accessibilit´ des membres . . . . . . . . . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   33
                            e e
      4.2.1 Membres prot´g´s . . . . . . . . . . . . . . . .          .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   33
               e           e       e e
      4.2.2 H´ritage priv´, prot´g´, public . . . . . . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   34
           e
  4.3 Red´finition des fonctions membres . . . . . . . . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   36
         e                                   e e
  4.4 Cr´ation et destruction des objets d´riv´s . . . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   37
        e                       e
  4.5 R´capitulation sur la cr´ation et destruction des objets        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   38
      4.5.1 Construction . . . . . . . . . . . . . . . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   38
      4.5.2 Destruction . . . . . . . . . . . . . . . . . . . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   38
  4.6 Polymorphisme . . . . . . . . . . . . . . . . . . . . . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   39
      4.6.1 Conversion standard vers une classe de base . .           .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   39
                                                   e e
      4.6.2 Type statique, type dynamique, g´n´ralisation             .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   41
  4.7 Fonctions virtuelles . . . . . . . . . . . . . . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   43
  4.8 Classes abstraites . . . . . . . . . . . . . . . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   45
  4.9 Identification dynamique du type . . . . . . . . . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   47
      4.9.1 L’op´rateur dynamic cast . . . . . . . . . . . .
                  e                                                   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   47
                  e
      4.9.2 L’op´rateur typeid . . . . . . . . . . . . . . .          .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   48

      e
5 Mod`les (templates)                                                                                                                                             49
           e
  5.1 Mod`les de fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                                    49
           e
  5.2 Mod`les de classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                                    51
                                            e
      5.2.1 Fonctions membres d’un mod`le . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                                           52

6 Exceptions                                                                                                                                                      53
  6.1 Principe et syntaxe . . . .     . . . . . . . . . . . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   53
  6.2 Attraper une exception . .      . . . . . . . . . . . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   55
       e
  6.3 D´claration des exceptions                               e
                                      qu’une fonction laisse ´chapper         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   56
  6.4 La classe exception . . . .     . . . . . . . . . . . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   57

                                                                                                           2 mars 2002
                                                                                      henri.garreta@luminy.univ-mrs.fr




                                                          2
1        e        e
       El´ments pr´alables
                                                                         ee
    Ce document est le support du cours sur le langage C++, consid´r´ comme une extension de C, langage
  e     e
pr´sum´ bien connu.
                    e                         ee        e                                               e
    Attention, la pr´sentation faite ici est d´s´quilibr´e : des concepts importants ne sont pas expliqu´s, pour
                       e e                                          e
la raison qu’ils sont r´alis´s en C++ comme en C, donc suppos´s acquis. En revanche, nous insistons sur les
diff´rences entre C et C++ et notamment sur tous les ´l´ments orient´s objets que C++ ajoute a C.
    e                                                     ee                e                          `
                e                                                       e                  e a      e
   Cette premi`re section expose un certain nombre de notions qui, sans ˆtre directement li´s ` la m´thodologie
              ea        ıtre                    e
objets, font d´j` apparaˆ C++ comme une am´lioration notable de C.

1.1                       e
           Placement des d´clarations de variables
               e                                      ıtre   e
    En C les d´clarations de variables doivent apparaˆ au d´but d’un bloc. En C++, au contraire, on peut
mettre une d´claration de variable partout o` on peut mettre une instruction1 .
             e                               u
             e           ıt
    Cette diff´rence paraˆ mineure, mais elle est importante par son esprit. Elle permet de repousser la
 e                                a                          u                       ee
d´claration d’une variable jusqu’` l’endroit du programme o` l’on dispose d’assez ´l´ments pour l’initiali-
                                           ea e      e                           e
ser. On lutte aussi contre les variables d´j` d´clar´es mais non encore initialis´es qui sont un important
vivier de bugs dans les programmes.
     Exemple, l’emploi d’un fichier. Version C :
       {
                FILE *fic;
                ...
                                                       `
                obtention de nomFic, le nom du fichier a ouvrir
                                                         e
                Danger ! Tout emploi de fic ici est erron´
                ...
                fic = fopen(nom, "w");
                ...
                                          e
                ici, l’emploi de fic est l´gitime
                ...
       }
Version C++ :
       {
                ...
                                                        `
                obtention de nomFic, le nom du fichier a ouvrir
                                                  a
                ici pas de danger d’utiliser fic ` tort
                ...
                FILE *fic = fopen(nom, "w");
                ...
                                          e
                ici, l’emploi de fic est l´gitime
                ...
       }

1.2            e
           Bool´ens
                         e
    En plus des types d´finis par l’utilisateur (ou classes, une des notions fondamentales de la programmation
      e                      e                                    `                            e
orient´e objets) C++ poss`de quelques types qui manquaient a C, notamment le type bool´en et les types
 ee
r´f´rences.
                              e
    Le type bool (pour bool´en) comporte deux valeurs : false et true. Contrairement a C :`
          e                e                                              e
    – le r´sultat d’une op´ration logique (&&, ||, etc.) est de type bool´en,
       a u                                                                          e
    – l` o` une condition est attendue on doit mettre une expression de type bool´en et
              e        e                                     e        e
    – il est d´conseill´ de prendre les entiers pour des bool´ens et r´ciproquement.
         e                    e
     Cons´quence pratique : n’´crivez pas              if (x) ...        au lieu de     if (x != 0) ...
    1 Bien                 e                                                    e          e        e              e e e     e
             entendu, une r`gle absolue reste en vigueur : une variable ne peut ˆtre utilis´e qu’apr`s qu’elle ait ´t´ d´clar´e.



                                                                     3
1.3       ee
         R´f´rences
1.3.1    Notion
      oe                      ee                           e                                            e
   A cˆt´ des pointeurs, les r´f´rences sont une autre mani`re de manipuler les adresses des objets plac´s dans
    e            ee                          ee         e                                                  e
la m´moire. Une r´f´rence est un pointeur g´r´ de mani`re interne par la machine. Si T est un type donn´, le
       ee
type r´f´rence sur T se note T &. Exemple :
        int i;
        int & r = i;                                ee
                                      // r est une r´f´rence sur i
                         ee                                                                        e
    Une valeur de type r´f´rence est une adresse mais, hormis lors de son initialisation, toute op´ration effectu´e e
        ee                         ee      e                           e
sur la r´f´rence agit sur l’objet r´f´renc´, non sur l’adresse. Il en d´coule qu’il est obligatoire d’initialiser une
 ee                    e           e
r´f´rence lors de sa cr´ation ; apr`s, c’est trop tard :
        r = j;                                                           ee
                                     // ceci ne transforme pas r en une r´f´rence
                                     // sur j mais copie la valeur de j dans i

1.3.2     ee             e
         R´f´rences param`tres des fonctions
           e                  ee                                                         e
   L’utilit´ principale des r´f´rences est de permettre de donner aux fonctions des param`tres modifiables,
sans utiliser explicitement les pointeurs. Exemple :
        void permuter(int & a, int & b) {
            int w = a;
            a = b; b = w;
            }
   Lors d’un appel de cette fonction, comme
        permuter(t[i], u);
           e                                  e                               e
ses param`tres formels a et b sont initialis´s avec les adresses des param`tres effectifs t[i] et u, mais cette
                                   e                              `                               `
utilisation des adresses reste cach´e, le programmeur n’a pas a s’en occuper. Autrement dit, a l’occasion d’un
                                                                                                            e
tel appel, a et b ne sont pas des variables locales de la fonction recevant des copies des valeurs des param`tres,
                                                                 e
mais d’authentiques synonymes des variables t[i] et u. Il en r´sulte que l’appel ci-dessus permute effectivement
les valeurs des variables t[i] et u2 .

1.3.3                             ee
         Fonctions renvoyant une r´f´rence
                     e                                       ee              e                           e
   Il est possible d’´crire des fonctions qui renvoient une r´f´rence comme r´sultat. Cela leur permet d’ˆtre le
                                                       `                ee
membre gauche d’une affectation, ce qui donne lieu a des expressions ´l´gantes et efficaces.
                                                                                      e
   Par exemple, voici comment une fonction permet de simuler un tableau index´ par des chaˆ         ınes de ca-
ract`res3 :
    e
        char *noms[N];
        int ages[N];

        int & age(char *nom) {
            for (int i = 0; i < N; i++)
                if (strcmp(nom, noms[i]) == 0)
                    return ages[i];
        }
                                e                                      `        e a
    Une telle fonction permet l’´criture d’expressions qui ressemblent a des acc`s ` un tableau dont les indices
seraient des chaˆınes :
               e
        age("Am´lie") = 20;
ou encore
        age("Benjamin")++;
   2 Notez qu’une telle chose est impossible en C, o` une fonction appel´e par l’expression permuter(t[i], u) ne peut recevoir
                                                    u                     e
que des copies des valeurs de de t[i] et u.
   3 C’est un exemple simpliste, pour faire court nous n’y avons pas trait´ le cas de l’absence de la chaˆ
                                                                          e                                        e
                                                                                                         ıne cherch´e.


                                                              4
1.3.4      ee                    e
          R´f´rences sur des donn´es constantes
              e                                                                       e                   ee
   Lors de l’´criture d’une fonction il est parfois souhaitable d’associer l’efficacit´ des arguments r´f´rences
                            ee                                                               e     e
(puisque dans le cas d’une r´f´rence il n’y a pas recopie de la valeur de l’argument) et la s´curit´ des arguments
                                           e          e
par valeur (qui ne peuvent en aucun cas ˆtre modifi´s par la fonction).
                             e                                        ee
   Cela peut se faire en d´clarant des arguments comme des r´f´rences sur des objets immodifiables, ou
           `
constants, a l’aide du qualifieur const :
        void uneFonction(const unType & arg) {
            ...
                                                                          ee
            ici arg n’est pas une recopie de l’argument effectif (puisque r´f´rence)
                                e          e
            mais il ne peut pas ˆtre modifi´ par la fonction (puisque const)
            ...
        }

1.3.5      ee
          R´f´rences ou pointeurs ?
                                                               u      ee            e e
   Il existe quelques situations, nous les verrons plus loin, o` les r´f´rences se r´v`lent indispensables. Cepen-
                                                                                                  e
dant, la plupart du temps elles font double emploi avec les pointeurs et il n’existe pas de crit`res simples pour
             ee            o
choisir des r´f´rences plutˆt que des pointeurs ou le contraire.
                                               e a             e e              e
   Par exemple, la fonction permuter montr´e ` la page pr´c´dente peut s’´crire aussi :
        void permuter(int *a, int *b) {
            int w = *a;
            *a = *b; *b = w;
            }
            e
son appel s’´crit alors
        permuter(&t[i], &u);

                                    e        a                                 e     e
   Attention On notera que l’op´rateur & (` un argument) a une signification tr`s diff´rente selon le contexte
                      ıt
dans lequel il apparaˆ :
               e           e
   – employ´ dans une d´claration, comme dans
            int &r = x;
              a                   ee
      il sert ` indiquer un type r´f´rence : r est une r´f´rence sur un int
                                                        ee
               e                        e                      e
   – employ´ ailleurs que dans une d´claration il indique l’op´ration obtention de l’adresse , comme dans
                                                               a
      l’expression suivante qui signifie affecter l’adresse de x ` p :
            p = &x;
                                                                      e              a
   – enfin, on doit se souvenir qu’il y a en C++, comme en C, un op´rateur & binaire (` deux arguments) qui
                                  a
      exprime la conjonction bit-`-bit de deux mots-machine.

1.4      Fonctions en ligne
                                                             e          a            u
    Normalement, un appel de fonction est une rupture de s´quence : ` l’endroit o` un appel figure, la machine
           e        e                                                                              e
cesse d’ex´cuter s´quentiellement les instructions en cours ; les arguments de l’appel sont dispos´s sur la pile
    e                 e                         a u
d’ex´cution, et l’ex´cution continue ailleurs, l` o` se trouve le code de la fonction.
                                                       a u                                    ıt
    Une fonction en ligne est le contraire de cela : l` o` l’appel d’une telle fonction apparaˆ il n’y a pas de
              e
rupture de s´quence. Au lieu de cela, le compilateur remplace l’appel de la fonction par le corps de celle-ci, en
                                 `
mettant les arguments affectifs a la place des arguments formels.
                                        e                                           e
    Cela se fait dans un but d’efficacit´ : puisqu’il n’y a pas d’appel, pas de pr´paration des arguments, pas
                  e                                                                                        a
de rupture de s´quence, pas de retour, etc. Mais il est clair qu’un tel traitement ne peut convenir qu’` des
             e                 e                                          `
fonctions fr´quemment appel´es (si une fonction ne sert pas souvent, a quoi bon la rendre plus rapide ?) de
                                   e                       e      e
petite taille (sinon le code compil´ risque de devenir d´mesur´ment volumineux) et rapides (si une fonction
                 e                                                                    e
effectue une op´ration lente, le gain de temps obtenu en supprimant l’appel est n´gligeable).
                                               e         e                       e e       e
    En C++ on indique qu’une fonction doit ˆtre trait´e en ligne en faisant pr´c´der sa d´claration par le mot
inline :




                                                        5
      inline int abs(int x) {
          return x >= 0 ? x : -x;
          }
                                                   e
    Les fonctions en ligne de C++ rendent le mˆme service que les macros (avec arguments) de C, mais
                                                                           `
on notera que les fonctions en ligne sont plus fiables car, contrairement a ce qui se passe pour les macros, le
                                           o                   e
compilateur peut y effectuer tous les contrˆles syntaxiques et s´mantiques qu’il fait sur les fonctions ordinaires.
            e                              e                 u                  e                e
    La port´e d’une fonction en ligne est r´duite au fichier o` la fonction est d´finie. Par cons´quent, de telles
                e e         e                            e
fonctions sont g´n´ralement ´crites dans des fichiers en-tˆte (fichiers .h ), qui doivent ˆtre inclus dans tous
                                                                                            e
les fichiers comportant des appels de ces fonctions.

1.5                 e
       Valeurs par d´faut des arguments des fonctions
            e                                                           e
   Les param`tres formels d’une fonction peuvent avoir des valeurs par d´faut. Exemple :
      void trier(void *table, int nbr, int taille = sizeof(void *), bool croissant = true);
                                                   e                                           e
    Lors de l’appel d’une telle fonction, les param`tres effectifs correspondants peuvent alors ˆtre omis (ainsi que
                                            e                           e                        e
les virgules correspondantes), les param`tres formels seront initialis´s avec les valeurs par d´faut. Exemples :
      trier(t, n, sizeof(int), false);
      trier(t, n, sizeof(int));        // croissant = true
      trier(t, n);                     // taille = sizeof(void *), croissant = true

1.6    Surcharge des noms de fonctions
                                                                                               ee
    La signature d’une fonction est la suite des types de ses arguments formels (et quelques ´l´ments suppl´-e
                                                       e
mentaires, que nous verrons plus loin). Le type du r´sultat rendu par la fonction ne fait pas partie de sa
signature.
                                                                                        e
    La surcharge des noms des fonctions consiste en ceci : en C++ des fonctions diff´rentes peuvent avoir le
  e          a                                                e
mˆme nom, ` la condition que leurs signatures soient assez diff´rentes pour que, lors de chaque appel, le nombre
                                                                       ıt´            `
et les types des arguments effectifs permettent de choisir sans ambigu¨ e la fonction a appeler. Exemple :
      int puissance(int x, int n) {
          calcul de xn avec x et n entiers
      }
      double puissance(double x, int n) {
          calcul de xn avec x flottant et n entier
      }
      double puissance(double x, double y) {
          calcul de xy avec x et y flottants
      }
    On voit sur cet exemple l’int´rˆt de la surcharge des noms des fonctions : la mˆme notion abstraite x
                                   ee                                                   e
` la puissance n se traduit par des algorithmes tr`s diff´rents selon que n est entier (xn = x.x...x) ou r´el
a                                                       e     e                                                 e
(xn = en log x ) ; de plus, pour n entier, si on veut que xn soit entier lorsque x est entier, on doit ´crire deux
                                                                                                       e
fonctions distinctes, une pour x entier et une pour x r´el.
                                                          e
                                              `
    Or le programmeur n’aura qu’un nom a connaˆ                          e
                                                     ıtre, puissance. Il ´crira dans tous les cas
      c = puissance(a, b);
                                                                e
le compilateur se chargeant de choisir la fonction la plus adapt´e, selon les types de a et b.
                         e                                                                         e
   Notes. 1. Le type du r´sultat rendu par la fonction ne fait pas partie de la signature. Par cons´quent, on
                       e        a                          e                          e
ne peut pas donner le mˆme nom ` deux fonctions qui ne diff´rent que par le type du r´sultat qu’elles rendent.
                                              e                                     e
    2. Lors de l’appel d’une fonction surcharg´e, la fonction effectivement appel´e est celle dont la signature
                                                                                                             e
correspond avec les types des arguments effectifs de l’appel. S’il y a une correspondance exacte, pas de probl`me.
                                                       e
S’il n’y a pas de correspondance exacte, alors des r`gles complexes (trop complexes pur les expliquer ici)
                     e                      `                  e       e
s’appliquent, pour d´terminer la fonction a appeler. Malgr´ ces r`gles, il existe de nombreux cas de figure
                                           e
ambigus, que le compilateur ne peut pas r´soudre.


                                                        6
                                                                                 e                     ee
     Par exemple, si x est flottant et n entier, l’appel puissance(n, x) est erron´, alors qu’il aurait ´t´ correct
s’il y avait eu une seule d´finition de la fonction puissance (les conversions entier ↔ flottant n´cessaires
                             e                                                                          e
               ee
auraient alors ´t´ faites).

1.7               e                     e
        Appel et d´finition de fonctions ´crites en C
                                  e                                                         e
    Pour que la compilation et l’´dition de liens d’un programme avec des fonctions surcharg´es soient possibles,
le compilateur C++ doit fabriquer pour chaque fonction un nom long comprenant le nom de la fonction et
         e              e                                                        ea e
une repr´sentation cod´e de sa signature ; c’est ce nom long qui est communiqu´ ` l’´diteur de liens.
    Or, les fonctions produites par un compilateur C n’ont pas de tels noms longs ; si on ne prend pas de
                     e                                                                              e
disposition particuli`re, il sera donc impossible d’appeler dans un programme C++ une fonction ´crite en C,
ou r´ciproquement. On rem´die ` cette impossibilit´ par l’utilisation de la d´claration extern ”C” :
    e                         e     a                  e                      e
      extern "C" {
           e               e           ee
          d´clarations et d´finitions d’´l´ments
                              e     e`         e
          dont le nom est repr´sent´ a la mani`re de C
      }

1.8         e
        Entr´es-sorties simples
                                                                       e                a             e
   Cette section traite de l’utilisation simple des flux standard d’entr´e-sortie, c’est-`-dire la mani`re de faire
               e
en C++ les op´rations qu’on fait habituellement en C avec les fonctions printf et scanf.
                                                   e
   Un programme qui utilise les flux standard d’entr´e-sortie doit comporter la directive
      #include <iostream.h>
ou bien, si vous utilisez un compilateur r´cent et que vous suivez de pr`s les recommandations de la norme4 :
                                          e                             e
      #include <iostream>
      using namespace std;
                    e                 e    e                                                e e    e        e
    Les flux d’entr´e-sortie sont repr´sent´s dans les programmes par les trois variables, pr´d´clar´es et pr´ini-
      e
tialis´es, suivantes :
                                   e    e                                                        e
    – cin, le flux standard d’entr´e (l’´quivalent du stdin de C), qui est habituellement associ´ au clavier du
       poste de travail,
                                          e                                                          ea e
    – cout, le flux standard de sortie (l’´quivalent du stdout de C), qui est habituellement associ´ ` l’´cran
       du poste de travail,
                                                                     e                                e
    – cerr, le flux standard pour la sortie des messages d’erreur (l’´quivalent du stderr de C), ´galement
             ea e
       associ´ ` l’´cran du poste de travail.
        e                                  e                                                `              e
    Les ´critures et lectures sur ces unit´s ne se font pas en appelant des fonctions, mais a l’aide des op´rateurs
<<, appel´ op´rateur d’injection ( injection de donn´es dans un flux de sortie), et >>, appel´ op´rateur
           e e                                               e                                          e e
                                       e                    e            e
d’extraction ( extraction de donn´es d’un flux d’entr´e). Or, le m´canisme de la surcharge des op´rateurs   e
                                e                              e a         `e
(voir la section 3) permet la d´tection des types des donn´es ` lire ou a ´crire. Ainsi, le programmeur n’a pas
` s’encombrer avec des sp´cifications de format.
a                           e
                                         e
    La syntaxe d’un injection de donn´e sur la sortie standard cout est :
                         `e
      cout << expression a ´crire
    e                                                 e                                            e
le r´sultat de cette expression est l’objet cout lui-mˆme. On peut donc lui injecter une autre donn´e, puis
encore une, etc. :
      ((cout << expression ) << expression ) << expression
            e          e                `                               e                e
ce qui, l’op´rateur << ´tant associatif a gauche, se note aussi, de mani`re bien plus agr´able :
      cout << expression << expression << expression
    4 C’est-`-dire, si vous utilisez les espaces de noms, ou namespace (tous les ´l´ments de la biblioth`que standard sont dans
            a                                                                    ee                     e
l’espace de noms std).




                                                              7
      e         e e
  Le mˆme proc´d´ existe avec l’extraction depuis cin. Par exemple, le programme suivant est un programme
C++ complet. Il calcule xn (pour x flottant et n entier).
         #include <iostream.h>

         double puissance(double x, int n) {
              algorithme de calcul de xn
         }

       void main() {
            double x;
            int n;
            cout << "Donne x et n : ";
            cin >> x >> n;
            cout << x << "^" << n << " = " << puissance(x, n) << "\n";
       }
               e
   Exemple d’ex´cution de ce programme :
         Donne x et n : 2 10
         2^10 = 1024


1.9                             e
       Allocation dynamique de m´moire
            e
    Des diff´rences entre C et C++ existent aussi au niveau de l’allocation et de la restitution dynamique de
  e
m´moire.
                                                e
    Les fonctions malloc et free de la biblioth`que standard C sont disponibles en C++. Mais il est fortement
        e            ee          e                                                                           ee
conseill´ de leur pr´f´rer les op´rateurs new et delete. La raison principale est la suivante : les objets cr´´s
a                             e a
` l’aide de new sont initialis´s ` l’aide des constructeurs (cf. section 2.4) correspondants, ce que ne fait pas
               e                     e                                  e
malloc. De mˆme, les objets liber´s en utilisant delete sont finalis´s en utilisant le destructeur (cf. section
                                                  `
2.6) de la classe correspondante, contrairement a ce que fait free.
    – Pour allouer un unique objet :
           new type
   – Pour allouer un tableau de n objets :
           new type[n]
                                                                                 a
   Dans les deux cas, new renvoie une valeur de type pointeur sur un type, c’est-`-dire       type * . Exemples
                                     e
(on suppose que Machin est un type d´fini par ailleurs) :
      Machin *ptr = new Machin;                // un objet Machin
      int *tab = new int[n];                   // un tableau de n int
                               e                          e                        e
   Si type est une classe poss´dant un constructeur par d´faut, celui-ci sera appel´ une fois (cas de l’allocation
d’un objet simple) ou n fois (allocation d’un tableau d’objets) pour construire l’objet ou les objets allou´s. Si
                                                                                                             e
                                            e                            e
type est une classe sans constructeur par d´faut, une erreur sera signal´e par le compilateur.
   Pour un tableau, la dimension n peut ˆtre donn´e par une variable, c’est-`-dire ˆtre inconnue lors de la
                                            e          e                         a     e
                                                   e                     e
compilation, mais la taille des composantes doit ˆtre connue. Il en d´coule que dans le cas d’un tableau a       `
                                 e                   e
plusieurs indices, seule la premi`re dimension peut ˆtre non constante :
      double (*M)[10];                   // pointeur de tableaux de 10 double
      ...
      acquisition de la valeur de n
      ...
      M = new double[n][10];             // allocation d’un tableau de n tableaux de 10 double
                 e          e                   a
M pourra ensuite ˆtre utilis´ comme une matrice ` n lignes et 10 colonnes.
         e                         e                                     ee
   L’op´rateur delete restitue la m´moire dynamique. Si la valeur de p a ´t´ obtenue par un appel de new,
   e
on ´crit


                                                        8
       delete p;
dans le cas d’un objet qui n’est pas un tableau, et
       delete [] p;
                                           e                                     a u
si ce que p pointe est un tableau. Les cons´quences d’une utilisation de delete l` o` il aurait fallu utiliser
                                    e
delete[], ou inversement, sont impr´visibles.


2      Classes
2.1        Classes et objets
                         e                                        e      e              e
   Un objet est constitu´ par l’association d’une certaine quantit´ de m´moire, organis´e en champs, et d’un
                              e                 `
ensemble de fonctions, destin´es principalement a la consultation et la modification des valeurs de ces champs.
         e
   La d´finition d’un type objet s’appelle une classe. D’un point de vue syntaxique, cela ressemble beaucoup
a       e
` une d´finition de structure, sauf que
   – le mot r´serv´ class remplace5 le mot struct,
              e   e
   – certains champs de la classe sont des fonctions.
                                                      e                                    e a      e
   Par exemple, le programme suivant est une premi`re version d’une classe Point destin´e ` repr´senter les
            e               e
points affich´s dans une fenˆtre graphique :
             class Point {
             public:
                 void afficher() {
                     cout << ’(’ << x << ’,’ << y << ’)’;
                 }
                 void placer(int a, int b) {
                     validation des valeurs de a et b;
                     x = a; y = b;
                 }
             private:
                 int x, y;
             };
                                                             e                e
    Chaque objet de la classe Point comporte un peu de m´moire, compos´e de deux entiers x et y, et de deux
                               e a
fonctions : afficher, qui acc`de ` x et y sans les modifier, et placer, qui change les valeurs de x et y.
                                                                                     e                 e
    L’association de membres et fonctions au sein d’une classe, avec la possibilit´ de rendre priv´s certains
                                                   e       ee          e                             ee e
d’entre eux, s’appelle l’encapsulation des donn´es. Int´rˆt de la d´marche : puisqu’elles ont ´t´ d´clar´es   e
     e                e                                  e         e
priv´es, les coordonn´es x et y d’un point ne peuvent ˆtre modifi´es autrement que par un appel de la fonction
                                            e           e                  e
placer sur ce point. Or, en prenant les pr´cautions n´cessaires lors de l’´criture de cette fonction (ce que nous
avons not´ validation des valeurs de a et b ) le programmeur responsable de la classe Point peut garantir
           e
                                                       e                                e
aux utilisateurs de cette classe que tous les objets cr´es auront toujours des coordonn´es correctes. Autrement
                                                        e
dit : chaque objet peut prendre soin de sa propre coh´rence interne.
                                              `                              e                     e
    Autre avantage important : on pourra a tout moment changer l’impl´mentation (i.e. les d´tails internes)
                                                            `
de la classe tout en ayant la certitude qu’il n’y aura rien a changer dans les programmes qui l’utilisent.
                                e                                                                     e
    Note. Dans une classe, les d´clarations des membres peuvent se trouver dans un ordre quelconque, mˆme
                        ee                                          e e
lorsque ces membres se r´f´rencent mutuellement. Dans l’exemple pr´c´dent, le membre afficher mentionne
                             e                     e
les membres x et y, dont la d´finition se trouve apr`s celle de afficher.
     Jargon. On appelle
                     e
     – objet une donn´e d’un type classe ou structure,
     – fonction membre un membre d’une classe qui est une fonction6 ,
     – donn´e membre un membre qui est une variable7 .
            e
    5 Enfait on peut aussi utiliser struct, voyez la section 2.2.5.
    6 Dans la plupart des langages orient´s objets, les fonctions membres sont appel´es m´thodes.
                                         e                                          e    e
   7 Dans beaucoup de langages orient´s objets, les donn´es membres sont appel´es variables d’instance et aussi, sous certaines
                                        e                   e                     e
                   ee
conditions, propri´t´s




                                                              9
2.2         e
         Acc`s aux membres
2.2.1         e
           Acc`s aux membres d’un objet
            e                                                    e
    On acc`de aux membres des objets en C++ comme on acc`de aux membres des structures en C. Par
          `                 e                              e    e e                 e
exemple, a la suite de la d´finition de la classe Point donn´e pr´c´demment on peut d´clarer des variables de
cette classe en ´crivant8 :
                e
        Point a, b, *pt;                   // deux points et un pointeur de point
                      u                             e                                      e
    Dans un contexte o` le droit de faire un tel acc`s est acquis (cf. section 2.2.3) l’acc`s aux membres du point
    e
a s’´crit :
        a.x = 0;                                    e       e
                                           // un acc`s bien ´crit au membre x du point a
        d = a.distance(b);                                  e
                                           // un appel bien ´crit de la fonction distance de l’objet a
                                      ee           e
   Si on suppose que le pointeur pt a ´t´ initialis´, par exemple par une expression telle que
        pt = new Point;                    // allocation dynamique d’un point
             e                  e e        e
alors des acc`s analogues aux pr´c´dents s’´crivent :
        pt->x = 0;                                  e       e                                e
                                           // un acc`s bien ´crit au membre x du point point´ par pt
        d = pt->distance(b);                                                                   e
                                           // un appel de la fonction distance de l’objet point´ par pt
                       e a
   A propos de l’acc`s ` un membre d’un objet, deux questions se posent. Il faut comprendre qu’elles sont
     `          e
tout a fait ind´pendantes l’une de l’autre :
           e             e             a        e
   – l’acc`s est-il bien ´crit ? c’est-`-dire, d´signe-t-il bien le membre voulu de l’objet voulu ?
              e        e               a                           u      e                           e
   – cet acc`s est-il l´gitime ? c’est-`-dire, dans le contexte o` il est ´crit, a-t-on le droit d’acc`s sur le membre
                                                    e           e a
      en question ? La question des droits d’acc`s est trait´e ` la section 2.2.3.

2.2.2         e a                          e a       e
           Acc`s ` ses propres membres, acc`s ` soi-mˆme
                                                                        e                          e
   Quand des membres d’un objet apparaissent dans une expression ´crite dans une fonction du mˆme objet
                                 e `
on dit que ce dernier fait un acc`s a ses propres membres.
                            `                      e       e
   On a droit dans ce cas a une notation simplifi´e : on ´crit le membre tout seul, sans expliciter l’objet en
question. C’est ce que nous avons fait dans les fonctions de la classe Point :
        class Point {
            ...
            void afficher() {
                cout << ’(’ << x << ’,’ << y << ’)’;
            }
            ...
        };

                                                                                       `
   Dans la fonction afficher, les membres x et y dont il question sont ceux de l’objet a travers lequel on
          e
aura appel´ cette fonction. Autrement dit, lors d’un appel comme
        unPoint.afficher();
                                e          `
le corps de cette fonction sera ´quivalent a
        cout << ’(’ << unPoint.x << ’,’ << unPoint.y << ’)’;
                                                                                               ee      a
    Acces a soi-meme. Il arrive que dans une fonction membre d’un objet on doive faire r´f´rence ` l’objet
        ` `         ˆ
(tout entier) a travers lequel on a appel´ la fonction. Il faut savoir que dans une fonction membre9 on dispose
              `                          e
                                     e
de la pseudo variable this qui repr´sente un pointeur vers l’objet en question.
                                             e               e e                                   e e
    Par exemple, la fonction afficher peut s’´crire de mani`re ´quivalente, mais cela n’a aucun int´rˆt :
              void afficher() {
                  cout << ’(’ << this->x << ’,’ << this->y << ’)’;
              }
  8 Notez                            e    e                              e                                       ee
            que, une fois la classe d´clar´e, il n’est pas obligatoire d’´crire class devant Point pour y faire r´f´rence.
  9 Sauf   dans le cas d’une fonction membre statique, voir la section 2.8.


                                                                   10
                                                                                                  `
   Pour voir un exemple plus utile d’utilisation de this imaginons qu’on nous demande d’ajouter a la classe
                         e                                         e
Point deux fonctions bool´ennes, une pour dire si deux points sont ´gaux, une autre pour dire si deux points
         e                                     e                 e
sont le mˆme objet. Dans les deux cas le deuxi`me point est donn´ par un pointeur :
        class Point {
            ...
            bool pointEgal(Point *pt) {
                return pt->x == x && pt->y == y;
            }
            bool memePoint(Point *pt) {
                return pt == this;
            }
            ...
        };


2.2.3                            e
          Membres publics et priv´s
          e                                      e              e
    Par d´faut, les membres des classes sont priv´s. Les mots cl´s public et private permettent de modifier
                e
les droits d’acc`s des membres :
        class nom {
                          e    e               e
             les membres d´clar´s ici sont priv´s
        public:
                          e    e
             les membres d´clar´s ici sont publics
        private:
                          e    e               e
             les membres d´clar´s ici sont priv´s
        etc.
        };
                                                         ıtre
   Les expressions public: et private: peuvent apparaˆ un nombre quelconque de fois dans une classe.
               e    e    e                                    e                       a
Les membres d´clar´s apr`s private: (resp. public:) sont priv´s (resp. publics) jusqu’` la fin de la classe, ou
      a
jusqu’` la rencontre d’une expression public: (resp. private:).
                                            e        e e         u                             e         e
    Un membre public d’une classe peut ˆtre acc´d´ partout o` il est visible ; un membre priv´ ne peut ˆtre
   e e                                                                             e e
acc´d´ que depuis une fonction membre de la classe (les notions de membre prot´g´, cf. section 4.2.1, et de
classes et fonctions amies, cf. section 2.9, nuanceront cette affirmation).
    Si p est une expression de type Point :
    – dans une fonction qui n’est pas membre ou amie de la classe Point, les expressions p.x ou p.y pourtant
                                                 ıt´                   e   e                     e
      syntaxiquement correctes et sans ambigu¨ e, constituent des acc`s ill´gaux aux membres priv´s x et y de
      la classe Point,
                                                                      e e
    – les expressions p.afficher() ou p.placer(u, v) sont des acc`s l´gaux aux membres publics afficher
                          e                   e                e
      et placer, qui se r´solvent en des acc`s parfaitement l´gaux aux membres p.x et p.y.


2.2.4     Encapsulation au niveau de la classe
                                                         e    a
   Les fonctions membres d’une classe ont le droit d’acc´der ` tous les membres de la classe : deux objets
       e
de la mˆme classe ne peuvent rien se cacher. Par exemple, le programme suivant montre notre classe Point
        e                                                     `
augment´e d’une fonction pour calculer la distance d’un point a un autre :




                                                     11
           class Point {
           public:
               void afficher() {
                   cout << ’(’ << x << ’,’ << y << ’)’;
               }
               void placer(int a, int b) {
                   validation des valeurs de a et b;
                   x = a; y = b;
               }
               double distance(Point autrePoint) {
                   int dx = x - autrePoint.x;
                   int dy = y - autrePoint.y;
                   return sqrt(dx * dx + dy * dy);
               }
           private:
               int x, y;
           };
                                                          e                    e
   Lors d’un appel tel que p.distance(q) l’objet p acc`de aux membres priv´s x et y de l’objet q. On dit
que C++ pratique l’encapsulation au niveau de la classe, non au niveau de l’objet.
                                             `                        e
   On notera au passage que, contrairement a d’autres langages orient´s objets, en C++ encapsuler n’est pas
                                                                        e               e
cacher mais interdire. Les usagers d’une classe voient les membres priv´s de cette derni`re, mais ne peuvent
pas les utiliser.

2.2.5     Structures
                          e                                    e
    Une structure est la mˆme chose qu’une classe mais, par d´faut, les membres y sont publics. Sauf pour
                                                                 `
ce qui touche cette question, tout ce qui sera dit dans la suite a propos des classes s’appliquera donc aux
structures :
        struct nom {
                          e    e
             les membres d´clar´s ici sont publics
        private:
                          e    e               e
             les membres d´clar´s ici sont priv´s
        public:
                          e    e
             les membres d´clar´s ici sont publics
        etc.
        };

2.3       e
         D´finition des classes
2.3.1      e         e   e       e           e                e
          D´finition s´par´e et op´rateur de r´solution de port´e
                                              e                e    e a       e
    Tous les membres d’une classe doivent ˆtre au moins d´clar´s ` l’int´rieur de la formule classnom{...} ;
                   e
qui constitue la d´claration de la classe.
                                                                               e                     ` e
    Cependant, dans le cas des fonctions, aussi bien publiques que priv´es, on peut se limiter a n’´crire que
          e a       e                       e                                           e
leur en-tˆte ` l’int´rieur de la classe et d´finir le corps ailleurs, plus loin dans le mˆme fichier ou bien dans un
autre fichier.
                                                      e                      e
    Il faut alors un moyen pour indiquer qu’une d´finition de fonction, ´crite en dehors de toute classe, est en
 e e        e                                                                    e        e                e
r´alit´ la d´finition d’une fonction membre d’une classe. Ce moyen est l’op´rateur de r´solution de port´e, dont
la syntaxe est
                                                 NomDeClasse::
                                                                    e      e e
   Par exemple, voici notre classe Point avec la fonction distance d´finie s´par´ment :




                                                       12
            class Point {
            public:
                ...
                double distance(Point autrePoint);
                ...
            }
                                    e                                                   e
  Il faut alors, plus loin dans le mˆme fichier ou bien dans un autre fichier, donner la d´finition de la fonction
                                         e
 promise dans la classe Point. Cela s’´crit :
            double Point::distance(Point autrePoint) {
                int dx = x - autrePoint.x;
                int dy = y - autrePoint.y;
                return sqrt(dx * dx + dy * dy);
            };
       e                           `     e                     e        e                     e
    D´finir les fonctions membres a l’ext´rieur de la classe all`ge la d´finition de cette derni`re et la rend plus
                                               e                       e                                 e
compacte. Mais la question n’est pas qu’esth´tique, il y a une diff´rence de taille : les fonctions d´finies a    `
     e                                            e
l’int´rieur d’une classe sont implicitement qualifi´es en ligne (cf. section 1.4).
         e                                                      e        e e
    Cons´quence : la plupart des fonctions membres seront d´finies s´par´ment. Seules les fonctions courtes,
             e                 e     e          e    e
rapides et fr´quemment appel´es m´riteront d’ˆtre d´finies dans la classe.

2.3.2                    e                   e
           Fichier d’en-tˆte et fichier d’impl´mentation
   En programmation orient´e objets, programmer c’est d´finir des classes. Le plus souvent ces classes
                               e                                 e
sont destin´es ` ˆtre utilis´es dans plusieurs programmes, pr´sents et ` venir10 . Se pose alors la question :
            e a e           e                                  e         a
comment disposer le code d’une classe pour faciliter son utilisation ?
                            e    e e
   Voici comment on proc`de g´n´ralement :
   – les d´finitions des classes se trouvent dans des fichiers en-tˆte (fichiers .h , .hpp , etc.),
          e                                                       e
                                  e               e
   – chacun des ces fichiers en-tˆte contient la d´finition d’une seule classe ou d’un groupe de classes intime-
             e                      e
     ment li´es ; par exemple, la d´finition de notre classe Point pourrait constituer un fichier Point.h
           e                                                   e       `    e                           e
   – les d´finitions des fonctions membres qui ne sont pas d´finies a l’int´rieur de leurs classes sont ´crites
     dans des fichiers sources (fichiers .cpp ou .cp ),
                                                                 e
   – aux programmeurs utilisateurs de ces classes sont distribu´s :
     – les fichiers .h
                           e
     – le fichiers objets r´sultant de la compilation des fichiers .cpp
                                                 `                                e
   Par exemple, voici les fichiers correspondants a notre classe Point (toujours tr`s modeste) :
   Fichier Point.h :
            class Point {
            public:
                void placer(int a, int b) {
                    validation de a et b
                    x = a; y = b;
                }
                double distance(Point autrePoint);
            private:
                int x, y;
            };

 10 La    e            e                                        e                 e
         r´utilisabilit´ du code est une des motivations de la m´thodologie orient´e objets.




                                                                 13
   Fichier Point.cpp :

          #include "Point.h"
          #include <math.h>

          double Point::distance(Point autrePoint) {
              int dx = x - autrePoint.x;
              int dy = y - autrePoint.y;
              return sqrt(dx * dx + dy * dy);
          }

                                                                      e e e
    La compilation du fichier Point.cpp produira un fichier objet (nomm´ g´n´ralement Point.o ou Point.obj).
Dans ces conditions, la distribution de la classe Point sera compos´e des deux fichiers Point.h et
                                                                            e
                             e             ee           e                      e         e
Point.obj, ce dernier ayant ´ventuellement ´t´ transform´ en un fichier biblioth`que (nomm´ alors Point.lib
                         ¸
ou quelque chose comme ca). Bien entendu, tout programme utilisateur de la classe Point devra comporter la
directive
        #include "Point.h"
                         e e         e
et devra, une fois compil´, ˆtre reli´ au fichier Point.obj ou Point.lib.

2.4      Constructeurs
2.4.1     e
         D´finition de constructeurs
                                                          e
   Un constructeur d’une classe est une fonction membre sp´ciale qui :
           e
   – a le mˆme nom que la classe,
   – n’indique pas de type de retour,
   – ne contient pas d’instruction return.
       o                                                                                         `
   Le rˆle d’un constructeur est d’initialiser un objet, notamment en donnant des valeurs a ses donn´es    e
                                 `                                                          e      e
membres. Le constructeur n’a pas a s’occuper de trouver l’espace pour l’objet ; il est appel´ (imm´diatement)
   e                     ee                                                                ee
apr`s que cet espace ait ´t´ obtenu, et cela quelle que soit la sorte d’allocation qui a ´t´ faite : statique,
automatique ou dynamique, cela ne regarde pas le constructeur. Exemple :
          class Point {
          public:
              Point(int a, int b) {
                    validation des valeurs de a et b
                    x = a; y = b;
              }
              ... autres fonctions membres ...
          private:
              int x, y;
          };

                                                   e
   Un constructeur de la classe est toujours appel´, explicitement (voir ci-dessous) ou implicitement, lorsqu’un
                            ee
objet de cette classe est cr´´, et en particulier chaque fois qu’une variable ayant cette classe pour type est
 e
d´finie.
                     e                                                                    e
   C’est le couple d´finition de la variable + appel du constructeur qui constitue la r´alisation en C++ du
            e                         ee                            e                        e
concept cr´ation d’un objet . L’int´rˆt pour le programmeur est ´vident : garantir que, d`s leur introduction
                                                          e            a      e                      e
dans un programme, tous les objets sont garnis et coh´rents, c’est-`-dire ´viter les variables ind´finies, au
contenu incertain.
                       e                                                                       e
   Une classe peut poss´der plusieurs constructeurs, qui doivent alors avoir des signatures diff´rentes :




                                                       14
          class Point {
          public:
              Point(int a, int b) {
                  validation de a et b
                  x = a; y = b;
              }
              Point(int a) {
                  validation de a
                  x = a; y = 0;
              }
              Point() {
                  x = y = 0;
              }
              ...
          private:
              int x, y;
          };
                     e                          e
   L’emploi de param`tres avec des valeurs par d´faut permet de grouper des constructeurs. La classe suivante
    e        e                           e e
poss`de les mˆmes constructeurs que la pr´c´dente :
          class Point {
          public:
              Point(int a = 0, int b = 0) {
                  validation de a et b
                  x = a; y = b;
              }
              ...
          private:
              int x, y;
          };
                                                                  e     e    e                     e
    Comme les autres fonctions membres, les constructeurs peuvent ˆtre d´clar´s dans la classe et d´finis
                             e e                 e      e
ailleurs. Ainsi, la classe pr´c´dente pourrait s’´crire ´galement
          class Point {
          public:
              Point(int a = 0, int b = 0);
              ...
          private:
              int x, y;
          };
et, ailleurs :
          Point::Point(int a, int b) {
              validation de a et b
              x = a; y = b;
          }

   Deux remarques generales. 1. Comme l’exemple ci-dessus le montre, lorsqu’une fonction fait l’objet
                         ´ ´
       e                    e         e e                                       e                        e
d’une d´claration et d’une d´finition s´par´es, comme le constructeur Point, les ´ventuelles valeurs par d´faut
                              e                  e
des argument concernent la d´claration, non la d´finition.
                                                e                   e        e e
   2. Lorsqu’une fonction fait l’objet d’une d´claration et d’une d´finition s´par´es, les noms des arguments ne
                         e                    e                                                 e      e
sont utiles que pour la d´finition. Ainsi, la d´claration du constructeur Point ci-dessus peut s’´crire ´galement :




                                                       15
          class Point {
              ...
              Point(int = 0, int = 0);
              ...
          };


2.4.2    Appel des constructeurs
                                         e                   e
   Un constructeur est toujours appel´ lorsqu’un objet est cr´e, soit explicitement, soit implicitement. Les
                          e    e
appels explicites peuvent ˆtre ´crits sous deux formes :
        Point a(3, 4);
        Point b = Point(5, 6);
                                                            e
    Dans le cas d’un constructeur avec un seul param`tre, on peut aussi adopter une forme qui rappelle
                                                   a
l’initialisation des variables de types primitifs (` ce propos voir aussi la section 3.3.1) :
        Point e = 7;                       e        `
                                        // ´quivaut a : Point e = Point(7)
                   e                                                e
   Un objet allou´ dynamiquement est lui aussi toujours initialis´, au mois implicitement. Dans beaucoup de
                      e             e                       e
cas il peut, ou doit, ˆtre initialis´ explicitement. Cela s’´crit :
        Point *pt;
        ...
        pt = new Point(1, 2);
                                      e        e
    Les constructeurs peuvent aussi ˆtre utilis´s pour initialiser des objets temporaires, anonymes. En fait,
                                        e                          e    e                          a
chaque fois qu’un constructeur est appel´, un objet nouveau est cr´e, mˆme si cela ne se passe pas ` l’occasion
       e                                                                 e
de la d´finition d’une variable. Par exemple, deux objets sans nom, repr´sentant les points (0,0) et (3,4), sont
cr´´s dans l’instruction suivante11 :
  ee
        cout << Point(0, 0).distance(Point(3, 4)) << "\n";
                                                                                              e    a
    Note. L’appel d’un constructeur dans une expression comportant un signe = peut prˆter ` confusion, a       `
                                                                                                          e
cause de sa ressemblance avec une affectation. Or, en C++, l’initialisation et l’affectation sont deux op´rations
                                                                                                         `
distinctes, du moins lorsqu’elles concernent des variables d’un type classe : l’initialisation consiste a donner
           e         `                            u                  a                                `
une premi`re valeur a une variable au moment o` elle commence ` exister ; l’affectation consiste a remplacer
                                                                   e
la valeur courante d’une variable par une autre valeur ; les op´rations mises en œuvre par le compilateur,
                             e                                                    e
constructeur dans un cas, op´rateur d’affectation dans l’autre, ne sont pas les mˆmes.
                                                                                           e
    Comment distinguer le = d’une affectation de celui d’une initialisation ? Grossi`rement, lorsque l’ex-
                                                e                                 `
pression commence par un type, il s’agit d’une d´finition et le signe = correspond a une initialisation. Exemple :
        Point a = Point(1, 2);                     // Initialisation de a
                       e
    Cette expression cr´e la variable a et l’initialise en rangeant dans a.x et a.y les valeurs 1 et 2. En revanche,
lorsque l’expression ne commence pas par un type, il s’agit d’une affectation. Exemple :
        Point a;
        ...
        a = Point(1, 2);                         // Affectation de a
                                                       e                                e
    L’expression ci-dessus est une affectation ; elle cr´e un point anonyme de coordonn´es (1,2) et le recopie
sur la variable a en remplacement de la valeur courante de cette variable, construite peu avant. On arrive au
  e      e            e e                                                                `
mˆme r´sultat que pr´c´demment, mais au prix de deux initialisations et une affectation a la place d’une seule
initialisation.
  11 Ces objets anonymes ne pouvant servir ` rien d’autre dans ce programme, ils seront d´truits lorsque cette instruction aura
                                           a                                             e
e e e e
´t´ ex´cut´e.




                                                              16
2.4.3                     e
        Constructeur par d´faut
                            e                                  e        e             e
    Le constructeur par d´faut est un constructeur qui peut ˆtre appel´ sans param`tres : ou bien il n’en a pas,
                          e                          e                 o                               e
ou bien tous ses param`tres ont des valeurs par d´faut. Il joue un rˆle remarquable, car il est appel´ chaque
                         ee
fois qu’un objet est cr´´ sans qu’il y ait appel explicite d’un constructeur, soit que le programmeur ne le juge
                                              e
pas utile, soit qu’il n’en a pas la possibilit´ :

         Point   x;                                  //   e        `
                                                          ´quivaut a : Point x = Point()
         Point   t[10];                              //   produit 10 appels de Point()
         Point   *p = new Point;                     //   e        `
                                                          ´quivaut a : p = new Point()
         Point   *q = new Point[10];                 //   produit 10 appels de Point()

                                                                                   e            e
    Synthese d’un constructeur par defaut. Puisque tout objet doit ˆtre initialis´ lors de sa cr´ation,
            `                                  ´                                                                e
                     e                                                                          e
si le programmeur ´crit une classe sans aucun constructeur, alors le compilateur synth´tise un constructeur
      e
par d´faut comme ceci :
    – si la classe n’a ni objet membre, ni fonction virtuelle, ni classe de base (c’est le cas de notre classe
                                           e e                                            `                        e
       Point), alors le constructeur synth´tis´ est le constructeur trivial, qui consiste a ne rien faire. Les donn´es
                           ee                            ee             a               e          e
       membres seront cr´´es comme elles l’auraient ´t´ en C, c’est-`-dire initialis´es par z´ro s’il s’agit d’une
                              e      e      e
       variable globale, laiss´es ind´termin´es s’il s’agit d’une variable locale ou dynamique,
                                                                                                  e e
    – si la classe a des objets membres ou des classes de base, alors le constructeur synth´tis´ produit l’appel
                               e
       du constructeur par d´faut de chaque objet membre et de chaque classe de base.
                                                    e
    Attention. Si au moins un constructeur est d´fini pour une classe, alors aucun constructeur par d´faut e
           e e                               e                                                      e
n’est synth´tis´ par le compilateur. Par cons´quent, ou bien l’un des constructeurs explicitement d´finis est un
                   e                      e
constructeur par d´faut, ou bien toute cr´ation d’un objet devra expliciter des valeurs d’initialisation.

2.4.4   Constructeur par copie (clonage)
    Le constructeur par copie d’une classe C est un constructeur dont le premier param`tre est de type C
                                                                                          e
      ee                                        ee                                                   e
& (r´f´rence sur un C ) ou const C & (r´f´rence sur un C constant) et dont les autres param`tres, s’ils
                                e                               e                              e
existent, ont des valeurs par d´faut. Ce constructeur est appel´ lorsqu’un objet est initialis´ en copiant un
objet existant. Cela arrive parfois explicitement, mais souvent implicitement, notamment chaque fois qu’un
               e              e               `                                e                      a
objet est pass´ comme param`tre par valeur a une fonction ou rendu comme r´sultat par valeur (c.-`-d. autre
         ee
qu’une r´f´rence) d’une fonction.
                                  e                                                                     e
    Si le programmeur n’a pas d´fini de constructeur de copie pour une classe, le compilateur synth´tise un
constructeur par copie consistant en la recopie de chaque membre d’un objet dans le membre correspondant
                                                                                        `
de l’autre objet. Si ces membres sont de types primitifs ou des pointeurs, cela revient a faire la copie bit a
                                                                                                             `
bit d’un objet sur l’autre.
                                                                      ee             `                    e
    A titre d’exemple le programme suivant introduit une nouvelle vari´t´ de point ; a chacun est associ´e une
e                         ıne         e
´tiquette qui est une chaˆ de caract`res :
          class PointNomme {
          public:
              PointNomme(int a, int b, char *s = "") {
                  x = a; y = b;
                  label = new char[strlen(s) + 1];
                  strcpy(label, s);
              }
              ...
          private:
              int x, y;
              char *label;
          };
                      e
    La figure 1 repr´sente la structure de ces objets. Lorsqu’un objet comporte des pointeurs, comme ici,
                                                                                    e
l’information qu’il repr´sente (appelons-la l’ objet logique ) ne se trouve pas enti`rement incluse dans l’espace
                        e
contigu que le compilateur connaˆ (l’ objet technique ), car des morceaux d’information (dans notre exemple
                                  ıt



                                                          17
                                        x
                                        y
                                    label                           Un texte

                                                               e
                                        Fig. 1 – Un point avec ´tiquette


              e                      `                          e                           `
le texte de l’´tiquette) se trouvent a d’autres endroits de la m´moire. Ainsi, la copie bit a bit que fait le
                   e           e a
compilateur peut ˆtre inadapt´e ` de tels objets.
                           e                     `
   La figure 2 montre le r´sultat de la copie bit a bit d’un objet Point telle que la produirait, avec l’actuelle
 e
d´finition de la classe, une affectation telle que
      b = a;
                                                                                                  e
(a et b sont des variables de type Point). Bien entendu, la copie du pointeur n’a pas dupliqu´ la chaˆ     ıne
     e                                                           e      ıne.
point´e : les deux objets, l’original et la copie, partagent la mˆme chaˆ La plupart du temps ce partage n’est
                                ` e                                          e
pas souhaitable, car difficile a g´rer et dangereux : toute modification de l’´tiquette d’un des deux points se
 e                e
r´percutera imm´diatement sur l’autre.

                                       a                                           b
                                x     10                                    x     10
                                y     20                                    y     20
                            label               Un texte                label


                                    Fig. 2 – Copie   superficielle   d’un objet

         e               e           e
   Pour r´soudre ce probl`me il faut ´quiper notre classe d’un constructeur par copie :
         class PointNomme {
             ...
             PointNomme(PointNomme &p) {
                 x = p.x; y = p.y;
                 label = new char[strlen(p.label) + 1];
                 strcpy(label, p.label);
             }
             ...
         };
                                        ınera la duplication effective de la chaˆ de caract`res point´e, comme
   Maintenant, la copie d’un point entraˆ                                      ıne        e         e
sur la figure 3.

                              a                                            b
                       x     10                                     x     10
                       y     20                                     y     20
                   label               Un texte                 label               Un texte

                                           Fig. 3 – Copie    profonde



2.5    Construction des objets membres
                                               a
   Lorsque des membres d’une classe sont ` leur tour d’un type classe on dit que la classe a des objets
                                                   e
membres. L’initialisation d’un objet de la classe n´cessite alors l’initialisation de ces objets membres. Il en est
                   e                                                                                a
toujours ainsi, ind´pendamment du fait que l’on emploie ou non un constructeur explicite, et qu’` son tour ce
constructeur appelle ou non explicitement des constructeurs des objets membres.




                                                        18
    Lorsque les objets membres n’ont pas de constructeurs par d´faut, une syntaxe sp´ciale12 permet de pr´ciser
                                                               e                    e                    e
les arguments des constructeurs des membres :

                                  e
            NomDeLaClasse(param`tres)
                                     e                    e
                    : membre(param`tres), ... membre(param`tres) {
               corps du constructeur
            }
                                                              e
   A titre d’exemple, imaginons que notre classe Point ne poss`de pas de constructeur sans arguments, et
              e                                                                        e      e
qu’on doive d´finir une classe Segment ayant deux points pour membres (un segment est d´termin´ par deux
                                 e
points). Voici comment on devra ´crire son constructeur :
            class Segment {
                Point origine, extremite;
                int epaisseur;
            public:
                Segment(int ox, int oy, int ex, int ey, int ep)
                        : origine(ox, oy), extremite(ex, ey) {
                    epaisseur = ep;
                }
                ...
            };
   Note. Si la classe Point avait eu un constructeur sans arguments, le mauvais constructeur suivant aurait
       e
quand-mˆme fonctionn´ e
            class Segment {
                ...
                Segment(int ox, int oy, int ex, int ey, int ep) {
                    origine = Point(ox, oy);                              e
                                                          // Version erron´e
                    extremite = Point(ex, ey);
                    epaisseur = ep;
                }
                ...
            };
                                                e
mais il faut comprendre que cette version est tr`s maladroite, car faite de deux affectations (les deux lignes
                                                       e                                         `
qui forment le corps du constructeur ne sont pas des d´clarations). Ainsi, au lieu de se limiter a initialiser les
                                          e
membres origine et extr´mit´, on proc`de successivement `
                          e    e                             a
   – la construction de origine et extr´mit´ en utilisant le constructeur sans arguments de la classe Point,
                                          e   e
                                                        e
   – la construction de deux points anonymes, initialis´s avec les valeurs de ox, oy, ex et ey,
       e
   – l’´crasement des valeurs initiales de origine et extr´mit´ par les deux points ainsi construits.
                                                           e    e
                        e                                                   e          e
    Note. La syntaxe sp´ciale pour l’initialisation des objets membres peut ˆtre utilis´e aussi pour initialiser
        e                                                                         e e                   e
les donn´es membres de types primitifs. Par exemple, le constructeur de Segment pr´c´dent peut aussi s’´crire :
            class Segment {
                ...
                Segment(int ox, int oy, int ex, int ey, int ep)
                        : origine(ox, oy), extremite(ex, ey), epaisseur(ep) {
                }
                ...
            };

2.6      Destructeurs
            e         e                       a                                           `
   De la mˆme mani`re qu’il y a des choses ` faire pour initialiser un objet qui commence a exister, il y a
                         `                                   ıtre.
parfois des dispositions a prendre lorsqu’un objet va disparaˆ
 12 A                        e                                 e
        titre d’exercice on v´rifiera que, sans cette syntaxe sp´ciale, la construction des objets membres serait impossible.


                                                                 19
                                                 e               e                         e e e           e
    Un destructeur est une fonction membre sp´ciale. Il a le mˆme nom que la classe, pr´c´d´ du caract`re ~.
                    e
Il n’a pas de param`tre, ni de type de retour. Il y a donc au plus un destructeur par classe.
                                          e                                   e                          e
    Le destructeur d’une classe est appel´ lorsqu’un objet de la classe est d´truit, juste avant que la m´moire
       e                  e   ee               e
occup´e par l’objet soit r´cup´r´e par le syst`me.
                                                            `
    Par exemple, voici le destructeur qu’il faut ajouter a notre classe PointNomm´. Sans ce destructeur, la
                                                                                       e
destruction d’un point n’entraˆ                  e                        e          e
                               ınerait pas la lib´ration de l’espace allou´ pour son ´tiquette :
         class PointNomme {
             ...
             ~PointNomme() {
                 delete [] label;
             }
             ...
         };
                                       `        e                             e
    Notez que le destructeur n’a pas a s’inqui´ter de restituer l’espace occup´ par l’ objet technique lui-
  e        e                                                              ıne    e                       e
mˆme, form´, ici, par les variables x, y et label (le pointeur, non la chaˆ point´e). Cette restitution d´pend
             e
du type de m´moire que l’objet occupe, statique, automatique ou dynamique, et ne regarde pas le destructeur,
       e         e                                                                    e
de la mˆme mani`re que son allocation ne regardait pas le constructeur qui a initialis´ l’objet.
                                                            e
   Synthese du destructeur. Si le programmeur n’a pas ´crit de destructeur pour une classe, le compilateur
           `
         e                    e
en synth´tise un, de la mani`re suivante :
   – si la classe n’a ni objets membres ni classes de base (cf. section 4), alors il s’agit du destructeur trivial
                   `
     qui consiste a ne rien faire,
                                                                                       e e          a
   – si la classe a des classes de base ou des objets membres, le destructeur synth´tis´ consiste ` appeler les
                             e
     destructeurs des donn´es membres et des classes de base, dans l’ordre inverse de l’appel des constructeurs
     correspondants.

2.7     Membres constants
2.7.1       e
        Donn´es membres constantes
             e                             e          e
   Une donn´e membre d’une classe peut ˆtre qualifi´e const. Il est alors obligatoire de l’initialiser lors de la
                                                                  e         e
construction d’un objet, et sa valeur ne pourra par la suite plus ˆtre modifi´e.
                                                                                                       c
   A titre d’exemple voici une nouvelle version de la classe Segment, dans laquelle chaque objet re¸oit, lors
de sa cr´ation, un num´ro de s´rie qui ne doit plus changer au cours de la vie de l’objet :
        e                e       e
         class Segment {
             Point origine, extremite;
             int epaisseur;
             const int numeroDeSerie;
         public:
             Segment(int x1, int y1, int x2, int y2, int ep, int num);
             };
                           e
Constructeur, version erron´e :
        Segment::Segment(int x1, int y1, int x2, int y2, int ep, int num)
                  : origine(x1, y1), extremite(x2, y2) {
             epaisseur = ep;
             numeroDeSerie = num;              // ERREUR : tentative de modification d’une constante
        }
Constructeur, version correcte, en utilisant la syntaxe de l’initialisation des objets membres :
         Segment::Segment(int x1, int y1, int x2, int y2, int ep, int num)
                 : origine(x1, y1), extremite(x2, y2), numeroDeSerie(num) {
             epaisseur = ep;
         }



                                                       20
2.7.2   Fonctions membres constantes
                      ea                e                                       e               `
    Le mot const plac´ ` la fin de l’en-tˆte d’une fonction membre indique que l’´tat de l’objet a travers lequel
                     e                  e                                   e        e
la fonction est appel´e n’est pas chang´ du fait de l’appel. C’est une mani`re de d´clarer qu’il s’agit d’une
fonction de consultation de l’objet, non d’une fonction de modification :

         class Point {
             ...
             void placer(int a, int b);                      // modifie l’objet
             float distance(Point p) const;                  // ne modifie pas l’objet
             ...
         };
    A l’int´rieur d’une fonction const d’une classe C le pointeur this est de type const C * const
           e
                                                         e                           e         e
(pointeur constant vers un C constant) : l’objet point´ par this ne pourra pas ˆtre modifi´. Cela permet au
                                    e                           ee
compilateur d’autoriser certains acc`s qui, sans cela, auraient ´t´ interdits. Par exemple, examinons la situation
suivante :
         void uneFonction(const Point a) {
             Point b;
             ...
             double d = a.distance(b);
             ...
         }
                                                                                          e e
la qualification const de la fonction distance est indispensable pour que l’expression pr´c´dente soit accept´ee
                                                                                      `
par le compilateur. C’est elle seule, en effet, qui garantit que le point a, contraint a rester constant, ne sera
            e
pas modifi´ par l’appel de distance.
                   e                                            e                        e e
    Il est conseill´ de qualifier const toute fonction qui peu l’ˆtre : comme l’exemple pr´c´dent le montre, cela
e
´largit son champ d’application.
   Coexistence des fonctions constantes et non constantes. La qualification const d’une fonction
membre fait partie de sa signature. Ainsi, on peut surcharger une fonction membre non constante par une
                                   `                e        e                                     e
fonction membre constante ayant, a part cela, le mˆme en-tˆte. La fonction non constante sera appel´e sur les
objets non constants, la fonction constante sur les objets constants.
                                 ee       e                                           e
   On peut utiliser cette propri´t´ pour ´crire des fonctions qui n’effectuent pas le mˆme traitement ou qui
                     e             e                             e
ne rendent pas le mˆme type de r´sultat lorsqu’elles sont appel´es sur un objet constant et lorsqu’elles sont
      e                                                             e
appel´es sur un objet non constant. Exemple (se rappeler que le r´sultat rendu par une fonction ne fait pas
partie de sa signature) :

         class Point {
             int x, y;
         public:
             int X() const { return x; }
             int Y() const { return y; }
             int& X() { return x; }
             int& Y() { return y; }
             ...
         };
            e             e e                                 e     e
   Avec la d´claration pr´c´dente, les fonctions X et Y sont s´curis´es : sur un objet constant elles ne permettent
que la consultation, sur un objet non constant elles permettent la consultation et la modification :




                                                        21
          const Point a(2, 3);
          Point b(4,5);
          int r;
          ...
          r = a.X();          //     Oui
          a.X() = r;          //     ERREUR ( a.X() rend une valeur)
          r = b.X();          //     Oui
          b.X() = r;          //                           ee
                                     Oui ( b.X() rend une r´f´rence)


2.8      Membres statiques
                                 e                                                          o
  Chaque objet d’une classe poss`de son propre exemplaire de chaque membre ordinaire (bientˆt nous dirons
membre non statique) de la classe :
                   e                                     e                        e              e
  – pour les donn´es membres, cela signifie que de la m´moire nouvelle est allou´e lors de la cr´ation de
    chaque objet ;
                                                                   e         e
  – pour les fonctions membres, cela veut dire qu’elles ne peuvent ˆtre appel´es qu’en association avec un
    objet (on n’appelle pas la fonction f mais la fonction f sur l’objet x ).
               e                                     e                                 e e            e
    A l’oppos´ de cela, les membres statiques, signal´s par la qualification static pr´c´dant leur d´claration,
            e
sont partag´s par tous les objets de la classe. De chacun il n’existe qu’un seul exemplaire par classe, quel que
soit le nombre d’objets de la classe.
              e                                                                                     e
    Les donn´es et fonctions membres non statiques sont donc ce que dans d’autres langages orient´s objets on
                                 e                                       e                                   e
appelle variables d’instance et m´thodes d’instance, tandis que les donn´es et fonctions statiques sont appel´es
                                           e
dans ces langages variables de classe et m´thodes de classe.
               e                    e                              e             e     e
   La visibilit´ et les droits d’acc`s des membres statiques sont r´gis par les mˆmes r`gles que les membres
ordinaires.

2.8.1        e
         Donn´es membres statiques
          class Point {
              int x, y;
          public:
              static int nombreDePoints;
              Point(int a, int b) {
                  x = a; y = b;
                  nbrPoints++;
              }
          };
                           e
   Chaque objet Point poss`de ses propres exemplaires des membres x et y mais, quel que soit le nombre de
                 `               e
points existants a un moment donn´, il existe un seul exemplaire du membre nombreDePoints.
    Initialisation. La ligne mentionnant nombreDePoints dans la classe Point est une simple annonce ,
              e                                                 e                           e
comme une d´claration extern du langage C. Il faut encore cr´er et initialiser cette donn´e membre (ce qui,
                e                                                                e
pour une donn´e membre non statique, est fait par le constructeur lors de la cr´ation de chaque objet). Cela
                                 `      e                     e                  e            e
se fait par une formule analogue a une d´finition de variable, ´crite dans la port´e globale, mˆme s’il s’agit de
              e
membres priv´s :
        int Point::nombreDePoints = 0;
(la ligne ci-dessus doit ˆtre ´crite dans un fichier .cpp , non dans un fichier .h ) L’acc`s ` un membre
                         e    e                                                         e a
                                               e         e                e a
statique depuis une fonction membre de la mˆme classe s’´crit comme l’acc`s ` un membre ordinaire (voyez
     e a
l’acc`s ` nombreDePoints fait dans le constructeur Point ci-dessus).
         e a                                                                 `
   L’acc`s ` un membre statique depuis une fonction non membre peut se faire a travers un objet, n’importe
lequel, de la classe :




                                                      22
          Point a, b, c;
          ...
          cout << a.nombreDePoints << "\n";
                                                                           e         e               e
    Mais, puisqu’il y a un seul exemplaire de chaque membre statique, l’acc`s peut s’´crire aussi ind´pendamment
                                                                                                      e
de tout objet, par une expression qui met bien en ´vidence l’aspect variable de classe des donn´es membres
                                                   e
statiques :
          cout << Point::nombreDePoints << "\n";


2.8.2    Fonctions membres statiques
                                                    e a                    e
   Une fonction membre statique n’est pas attach´e ` un objet. Par cons´quent :
   – elle ne dispose pas du pointeur this,
                                 ee
   – de sa classe, elle ne peut r´f´rencer que les fonctions et les membres statiques.
                                        e e                                                   ee
   Par exemple, voici la classe Point pr´c´dente, dans laquelle le membre nombreDePoints a ´t´ rendu priv´    e
            e
pour en empˆcher toute modification intempestive. Il faut donc fournir une fonction pour en consulter la valeur,
                  e
nous l’avons appel´e combien :
          class Point {
              int x, y;
              static int nombreDePoints;
          public:
              static int combien() {
                  return nombreDePoints;
              }
              Point(int a, int b) {
                  x = a; y = b;
                  nbrPoints++;
              }
          };
                                                                 e                              e
   Pour afficher le nombre de points existants on devra maintenant ´crire une expression comme (a ´tant de
type Point) :
        cout << a.combien() << "\n";
ou, encore mieux, une expression qui ne fait pas intervenir de point particulier :
        cout << Point::combien() << "\n";

2.9      Amis
2.9.1    Fonctions amies
                                                                e                                            e
    Une fonction amie d’une classe C est une fonction qui, sans ˆtre membre de cette classe, a le droit d’acc´der
a                                               e
` tous ses membres, aussi bien publics que priv´s.
                            e      e   e       e                                                 e     e e e
    Une fonction amie doit ˆtre d´clar´e ou d´finie dans la classe qui accorde le droit d’acc`s, pr´c´d´e du
     e     e                e               e    e           e
mot r´serv´ friend. Cette d´claration doit ˆtre ´crite indiff´remment parmi les membres publics ou parmi les
              e
membres priv´s :
          class Tableau {
              int *tab, nbr;
              friend void afficher(const Tableau &);
          public:
              Tableau(int nbrElements);
              ...
          };
et, plus loin, ou bien dans un autre fichier :


                                                       23
         void afficher(const Tableau &t) {
             cout << ’[’;
             for (int i = 0; i < t.nbr; i++)
                  cout << ’ ’ << t.tab[i];
             cout << ]” ;”
         }
                                                                e    e a        e
   Note. Notez cet effet de la qualification friend : bien que d´clar´e ` l’int´rieur de la classe Tableau, la
                                                                                          e a
fonction afficher n’est pas membre de cette classe ; en particulier, elle n’est pas attach´e ` un objet, et le
                           e
pointeur this n’y est pas d´fini.
                   e e                             e                                             e
   Les exemples pr´c´dents ne montrent pas l’utilit´ des fonctions amies, ce n’est pas une chose ´vidente.
                                                                          e            e
En effet, dans la plupart des cas, une fonction amie peut avantageusement ˆtre remplac´e par une fonction
membre :
         class Tableau {
             int *tab, nbr;
         public:
             void afficher() const; //maintenant c’est une fonction membre
             ...
         };
         e
avec la d´finition :
          void Tableau::afficher() {
               cout << ’[’;
               for (int i = 0; i < nbr; i++)
                   cout << ’ ’ << tab[i];
               cout << ]” ;”
          }
                                       u                   e     e             e
    Il y a cependant des cas de figure o` une fonction doit ˆtre n´cessairement ´crite comme une amie d’une
                                                           u                                            e
classe et non comme un membre ; un de ces cas est celui o` la fonction doit, pour des raisons diverses, ˆtre
                                                                                      e      ee
membre d’une autre classe. Imaginons, par exemple, que la fonction afficher doive ´crire les ´l´ments d’un
objet Tableau dans un certain objet Fen^tre :
                                         e
         class Tableau;                    // ceci   promet   la classe Tableau

         class Fenetre {
             ostream &fluxAssocie;
         public:
             void afficher(const Tableau &);
             ...
         };

         class Tableau {
             int *tab, nbr;
         public:
             friend void Fenetre::afficher(const Tableau&);
             ...
         };
   Maintenant, la fonction afficher est membre de la classe Fenetre et amie de la classe Tableau : elle a
                                    e                              e                           e
tous les droits sur les membres priv´s de ces deux classes, et son ´criture s’en trouve facilit´e :
         void Fenetre::afficher(const Tableau &t) {
             for (int i = 0; i < t.nbr; i++)
                 fluxAssocie << t.tab[i];
         }




                                                     24
2.9.2   Classes amies
                                                                      e    a
    Une classe amie d’une classe C est une classe qui a le droit d’acc´der ` tous les membres de C. Une telle
            e     e    e                                                         e     e e e           e
classe doit ˆtre d´clar´e dans la classe C (la classe qui accorde le droit d’acc`s), pr´c´d´e du mot r´serv´e
              e                                 e
friend, indiff´remment parmi les membres priv´s ou parmi les membres publics de C.
                                                              e                             e
    Exemple : les deux classes Maillon et Pile suivantes impl´mentent la structure de donn´es pile (structure
              e
  dernier entr´ premier sorti ) d’entiers :
         class Maillon {
             int info;
             Maillon *suivant;
             Maillon(int i, Maillon *s) {
                 info = i; suivant = s;
             }
             friend class Pile;
         };

         class Pile {
             Maillon *top;
         public:
             Pile() {
                 top = 0;
             }
             bool vide() {
                 return top == 0;
             }
             void empiler(int x) {
                 top = new Maillon(x, top);
             }
             int sommet() {
                 return top->info;
             }
             void depiler() {
                 Maillon *w = top;
                 top = top->suivant;
                 delete w;
             }
         };
                              e                                                                e
    On notera la particularit´ de la classe Maillon ci-dessus : tous ses membres sont priv´s, et la classe Pile
est son amie (on dit que Maillon est une classe esclave de la classe Pile). Autrement dit, seules les piles
                  e                                                  e                                      e
ont le droit de cr´er et de manipuler des maillons ; le reste du syst`me n’utilise que les piles et leurs op´rations
                     e       a       ıtre
publiques, et n’a mˆme pas ` connaˆ l’existence des maillons.
    Exemple d’utilisation :
         Pile p;
         int x;

         cout << "? "; cin >> x;
         while (x >= 0) {
             p.empiler(x);
             cin >> x;
         }

         while (! p.vide()) {
             cout << p.sommet() << ’ ’;
             p.depiler();
         }

                                                        25
                      e                                                                              e
   La relation d’amiti´ n’est pas transitive, les amis de mes amis ne sont pas mes amis . A l’´vidence,
                 e                       e         e                        e              e           e
la notion d’amiti´ est une entorse aux r`gles qui r´gissent les droits d’acc`s ; elle doit ˆtre employ´e avec
                e                                         e
une grande mod´ration, et uniquement pour permettre l’´criture de composants intimement associ´s d’un  e
programme, comme les classes Maillon et Pile de notre exemple.


3                       e
        Surcharge des op´rateurs
3.1     Principe
                         e        e                e                                 e       a
   En C++ on peut red´finir la s´mantique des op´rateurs du langage, soit pour les ´tendre ` des objets, alors
      e                      e                                                              e          e e
qui n’´taient initialement d´finis que sur des types primitifs, soit pour changer l’effet d’op´rateurs pr´d´finis
                                                e
sur des objets. Cela s’appelle surcharger des op´rateurs.
                                                    e                     e         ea
   Il n’est pas possible d’inventer de nouveaux op´rateurs ; seuls des op´rateurs d´j` connus du compilateur
         e             e             e                          e            e
peuvent ˆtre surcharg´s. Tous les op´rateurs de C++ peuvent ˆtre surcharg´s, sauf les cinq suivants :
                           .           .*          ::           ?   :          sizeof
                                               e             e              `           e
     Il n’est pas possible de surcharger un op´rateur appliqu´ uniquement a des donn´es de type standard : un
   e                       e
op´rande au moins doit ˆtre d’un type classe.
                        e         e                            e              e                    e
     Une fois surcharg´s, les op´rateurs gardent leur pluralit´, leur priorit´ et leur associativit´ initiales. En
                             e                      e      e                e                            e
revanche, ils perdent leur ´ventuelle commutativit´ et les ´ventuels liens s´mantiques avec d’autres op´rateurs.
                    e                                              `e       e
Par exemple, la s´mantique d’une surcharge de ++ ou <= n’a pas a ˆtre li´e avec celle de + ou <.
                        e              ` e                                   ee     `
     Surcharger un op´rateur revient a d´finir une fonction ; tout ce qui a ´t´ dit a propos de la surcharge des
                                             `                   e
fonctions (cf. section 1.6) s’applique donc a la surcharge des op´rateurs.
     Plus pr´cis´ment, pour surcharger un op´rateur • (ce signe repr´sente un op´rateur quelconque) il faut
              e e                                e                      e             e
 e                            e                     e
d´finir une fonction nomm´e operator•. Ce peut ˆtre une fonction membre d’une classe ou bien une fonction
    e                                                                                              e
ind´pendante. Si elle n’est pas membre d’une classe, alors elle doit avoir au moins un param`tre d’un type
classe.

3.1.1                     e
         Surcharge d’un op´rateur par une fonction membre
                                                                                            e
   Si la fonction operator• est membre d’une classe, elle doit comporter un param`tre de moins que la
        e        e                      e                   `                              ee       e
pluralit´ de l’op´rateur : le premier op´rande sera l’objet a travers lequel la fonction a ´t´ appel´e. Ainsi, sauf
quelques exceptions :
    –    obj• ou •obj ´quivalent a obj .operator•()
                          e        `
    –    obj1 • obj2 ´quivaut a obj1 .operator•(obj2 )
                     e        `
    Exemple :
          class Point {
              int x, y;
          public:
              Point(int = 0, int = 0);
              int X() const { return x; }
              int Y() const { return y; }
              Point operator+(const Point) const;             //surcharge de + par une fonction membre
              ...
          };

        Point Point::operator+(const Point q) const {
             return Point(x + q.x, y + q.y);
        }
    Emploi :
          Point p, q, r;
          ...
          r = p + q;                 // compris comme : r = p.operator+(q);


                                                        26
3.1.2                    e
        Surcharge d’un op´rateur par une fonction non membre
                                                                                                    e     e
    Si la fonction operator• n’est pas membre d’une classe, alors elle doit avoir un nombre de param`tres ´gal
` la pluralit´ de l’op´rateur. Dans ce cas :
a            e        e
   –    obj• ou •obj ´quivalent a operator•(obj )
                         e        `
   –    obj1 • obj2 ´quivaut a operator•(obj1 , obj2 )
                    e        `
   Exemple :
       Point operator+(const Point p, const Point q) {                 // surcharge de + par une
            return Point(p.X() + q.X(), p.Y() + q.Y());                // fonction non membre
       }
   Emploi :
         Point p, q, r;
         ...
         r = p + q;                 // compris maintenant comme : r = operator+(p, q);

                                                                                                e
   Note 1. A cause des conversions implicites (voir la section 3.3.1), la surcharge d’un op´rateur binaire
    e                                              e e               e e      ee                       e
sym´trique par une fonction non membre, comme la pr´c´dente, est en g´n´ral pr´f´rable, car les deux op´randes
            e     e
y sont trait´s sym´triquement. Exemple :
         Point p, q, r;
         int x, y;
                    e
   Surcharge de l’op´rateur + par une fonction membre :
         r = p + q;                    // Oui : r = p.operator+(q);
         r = p + y;                    // Oui : r = p.operator+(Point(y));
         r = x + q;                    // Erreur : x n’est pas un objet
                    e
   Surcharge de l’op´rateur + par une fonction non membre :
         r = p + q;                   // Oui : r = operator+(p, q);
         r = p + y;                   // Oui : r = operator+(p, Point(y));
         r = x + q;                   // Oui : r = operator+(Point(p), q);
                                             e
   Note 2. Lorsque la surcharge d’un op´rateur est une fonction non membre, on a souvent int´rˆt, ou ee
n´cessit´, ` en faire une fonction amie. Par exemple, si la classe Point n’avait pas poss´d´ les accesseurs
 e      e a                                                                              e e
                                u
publics X() et Y(), on aurait dˆ surcharger l’addition par une fonction amie :
         class Point {
             int x, y;
         public:
             Point(int = 0, int = 0);
             friend Point operator+(const Point, const Point);
             ...
         };

          Point operator+(const Point p, Point q) {
               return Point(p.x + q.x, p.y + q.y);
          }
                                                              e
    Note 3. Les deux surcharges de + comme celles montr´es ci-dessus, par une fonction membre et par une
                                        e      e            e                    e
fonction non membre, ne peuvent pas ˆtre d´finies en mˆme temps dans un mˆme programme ; si tel ´tait       e
                                                   e          e
le cas, une expression comme p + q serait trouv´e ambigu¨ par le compilateur. On notera que cela est une
            e                       e                     e
particularit´ de la surcharge des op´rateurs, un tel probl`me ne se pose pas pour les fonctions ordinaires (une
                                       e
fonction membre n’est jamais en comp´tition avec une fonction non membre).
                                    e                                                        e
    Note 4. La surcharge d’un op´rateur binaire par une fonction non membre est carr´ment obligatoire
                     e                                                  e
lorsque le premier op´rande est d’un type standard ou d’un type classe d´fini par ailleurs, que le programmeur
              e
ne peut plus ´tendre (pour un exemple, voyez la section 3.2.1.


                                                      27
3.2     Quelques exemples
3.2.1                                   e
         Injection et extraction de donn´es dans les flux
    L’op´rateur <<13 peut ˆtre utilis´ pour injecter des donn´es dans un flux de sortie, ou ostream (cf.
        e                  e         e                           e
                                                           e
section 1.8). D’une part, l’auteur de la classe ostream a d´fini des surcharges de <<, probablement par des
                                                    e                         e
fonctions membres, pour les types connus lors du d´veloppement de la biblioth`que standard :

          class ostream {
              ...
          public:
              ...
              ostream& operator<<(int);
              ostream& operator<<(unsigned int);
              ostream& operator<<(long);
              ostream& operator<<(unsigned long);
              ostream& operator<<(double);
              ostream& operator<<(long double);
              etc.
              ...
          };
                                             e                                                         e
   D’autre part, le programmeur qui souhaite ´tendre << aux objets d’une classe qu’il est en train de d´velopper
                                 `                                 e
ne peut plus ajouter des membres a la classe ostream. Il doit donc ´crire une fonction non membre :
          ostream& operator<<(ostream& o, const Point p) {
              return o << ’(’ << p.X() << ’,’ << p.Y() << ’)’;
          }
                                       e               e
   Parfois (ce n’est pas le cas ici) l’´criture de l’op´rateur non membre est plus simple si on en fait une fonction
                        e
amie des classes des op´randes :
          class Point {
              ...
              friend ostream& operator<<(ostream&, const Point);
          };

        ostream& operator<<(ostream &o, const Point p) {
             return o << ’(’ << p.x << ’,’ << p.y << ’)’;
        }
                                           e                                           e
   Avec cette surcharge de << nos points s’´crivent sur un flux de sortie comme les donn´es primitives :
          Point p;
          ...
                                 e
          cout << "le point trouv´ est : " << p << "\n";
                    e                            e
   On peut de mani`re analogue surcharger l’op´rateur >> afin d’obtenir une moyen simple pour lire des
points. Par exemple, si on impose que les points soient donn´s sous la forme (x, y), c’est-`-dire par deux
                                                              e                            a
          e e                            e                 e
nombres s´par´s par une virgule et encadr´s par des parenth`ses :

          class Point {
              ...
              friend ostream& operator<<(ostream&, const Point);
              friend istream& operator>>(istream&, Point&);
          };

  13 Appliqu´ ` des donn´es de types primitifs, l’op´rateur << exprime, en C++ comme en C, l’op´ration de d´calage de bits vers
            ea          e                           e                                          e           e
la gauche.




                                                              28
       istream& operator>>(istream& i, Point& p) {
            char c;
            i >> c;
            if (c == ’(’) {
                  cin >> p.x >> c;
                  if (c == ’,’) {
                       cin >> p.y >> c;
                       if (c == ’)’)
                           return i;
                  }
            }
                                                       e
            cerr << "Erreur de lecture. Programme avort´\n";
            exit(-1);
       }
   Exemple d’utilisation :
         void main() {
              Point p;
              cout << "donne un point : ";
              cin >> p;
                                    e
              cout << "le point donn´ est : " << p << "\n";
         }
   Essai de ce programme :
         donne un point : ( 2 , 3 )
                      e
         le point donn´ est : (2,3)


3.2.2   Affectation
                                          e         e e                 e            e
    L’affectation entre objets est une op´ration pr´d´finie qui peut ˆtre surcharg´e. Si le programmeur ne le
                                                              e e           e
fait pas, tout se passe comme si le compilateur avait synth´tis´ une op´ration d’affectation consistant en la
                 a
recopie membre ` membre des objets. S’il s’agit d’une classe sans objets membres, sans classe de base et sans
                                   e                                                        `
fonction virtuelle, cela donne l’op´rateur d’affectation trivial, consistant en la copie bit a bit d’une portion de
  e
m´moire sur une autre, comme pour la copie des structures du langage C.
                              a                e                                                   e
    Les raisons qui poussent ` surcharger l’op´rateur d’affectation pour une classe sont les mˆmes que celles
              `e                                                        e                                  e
qui poussent a ´crire un constructeur par copie (cf. section 2.4.4). Tr`s souvent, c’est que la classe poss`de des
                          a
  bouts dehors , c’est-`-dire des membres de type pointeur, et qu’on ne peut pas se contenter d’une copie
superficielle.
        e                          e            e
    L’op´rateur d’affectation doit ˆtre surcharg´ par une fonction membre (en effet, dans une affectation on ne
                         e                   o       e
souhaite pas que les op´randes jouent des rˆles sym´triques).
                                   e                         ea
    Reprenons les points munis d’´tiquettes qui nous ont d´j` servi d’exemple :
         class PointNomme {
         public:
             PointNomme(int = 0, int = 0, char * = "");
             PointNomme(const PointNomme&);
             ~PointNomme();
         private:
             int x, y;
             char *label;
         };




                                                       29
         PointNomme::PointNomme(int a, int b, char *s) {
             x = a; y = b;
             label = new char[strlen(s) + 1];
             strcpy(label, s);
         }
         PointNomme::PointNomme(const PointNomme& p) {
             cout << "PointNomme(const PointNomme&)\n";
             x = p.x; y = p.y;
             label = new char[strlen(p.label) + 1];
             strcpy(label, p.label);
         }
         PointNomme::~PointNomme() {
              cout << "~PointNomme()\n";
              delete [] label;
         }
                                e
    Avec une classe PointNomme d´finie comme ci-dessus, un programme aussi        inoffensif   que le suivant est
     e
erron´ (et explosif) :
         void main() {
             PointNomme p(0, 0, "Origine"), q;
             q = p;
         }
                                       ee         e
    En effet, l’affectation n’ayant pas ´t´ surcharg´e, l’instruction q = p ; ne fait qu’une recopie bit a bit
                                                                                                           `
                                   a                                                a
de l’objet p dans l’objet q, c’est ` dire une copie superficielle (voyez la figure 2 ` la page 18). A la fin du
                                     e               e                                                    e
programme, les objets p et q sont d´truits, l’un apr`s l’autre. Or, la destruction d’un de ces objets lib`re la
    ıne                                  e
chaˆ label et rend l’autre objet incoh´rent, ce qui provoque une erreur fatale lors de la restitution du second
objet.
                              e                          e            e
    Voici la surcharge de l’op´rateur d’affectation qui r´sout ce probl`me. Comme il fallait s’y attendre, cela
    e       a                                                                u
se r´sume ` une destruction de la valeur courante (sauf dans le cas vicieux o` on essaierait d’affecter un objet
          e
par lui mˆme) suivie d’une copie :
         class PointNomme {
         public:
             PointNomme(int = 0, int = 0, char * = "");
             PointNomme(const PointNomme&);
             PointNomme& operator=(const PointNomme&);
             ~PointNomme();
             ...
         };

         PointNomme& PointNomme::operator=(const PointNomme& p) {
             if (&p != this)
                 delete [] label;
             x = p.x;
             y = p.y;
             label = new char[strlen(p.label) + 1];
             strcpy(label, p.label);
             return *this;
         }




                                                      30
3.3       e
        Op´rateurs de conversion
3.3.1   Conversion vers un type classe
                            e
   Pour convertir une donn´e de n’importe quel type, primitif ou classe, vers un type classe il suffit d’em-
ployer un constructeur de conversion. Un tel constructeur n’est pas une notion nouvelle, mais simplement un
                      e         e
constructeur qui peut ˆtre appel´ avec un seul argument. Exemple :
         class Point {
             ...
         public:
             Point(int a) {
                 x = y = a;
             }
             ...
         };
                                                                          e       e
   Le programmeur peut appeler explicitement ce constructeur de trois mani`res diff´rentes :
         Point p = Point(2);
         Point q(3);                  // compris comme Point q = Point(3);
         Point r = 4;                 // compris comme Point r = Point(4);
   La troisi`me expression ci-dessus fait bien apparaˆ l’aspect conversion de ce proc´d´. Il faut savoir
            e                                         ıtre                                    e e
                                       e                                                `                 u
qu’un tel constructeur sera aussi appel´ implicitement, car le compilateur s’en servira a chaque endroit o`, un
      e                                                   `
Point ´tant requis, il trouvera un nombre. Exemple (vu a la section 3.1.2) :
         Point p;
         ...
         r = p + 5;                   // compris comme : r = operator+(p, Point(5));
                                                         e
   Note. Si on les trouve trop dangereuses, on peut empˆcher que le compilateur fasse de telles utilisations
                             `
implicites d’un constructeur a un argument. Il suffit pour cela de le qualifier explicit :
        class Point {
               ...
        public:
               explicit Point(int a) {
                   x = y = a;
               }
               ...
        };
Nouvel essai :
         Point p = Point(1);          // Ok
         Point q = 2;                 // ERREUR

3.3.2   Conversion d’un objet vers un type primitif
     e                                                    e
   C ´tant une classe et T un type, primitif ou non, on d´finit la conversion de C vers T par une fonction
membre C ::operatorT ().
   Par exemple, les classes Point et Segment suivantes sont munies de conversions int ↔ Point et Point ↔
Segment :




                                                      31
         struct Point {
             int x, y;
             ...
             Point(int a) {                                // conversion int → Point
                 x = a; y = 0;
             }
             operator int() {                              // conversion Point → int
                 return abs(x) + abs(y);                   // par exemple...
             }
         };

         struct Segment {
             Point orig, extr;
             ...
             Segment(Point p)                    // conversion Point → Segment
                 : orig(p), extr(p) { }
             operator Point() {                  // conversion Segment → Point
                 return Point((orig.x + extr.x) / 2, (orig.y + extr.y) / 2);
             }
         };


4      e
      H´ritage
4.1                                e e
       Classes de base et classes d´riv´es
         e              e                      e                          e
    Le m´canisme de l’h´ritage consiste en la d´finition d’une classe par r´union des membres d’une ou plusieurs
           e                                                                             e
classes pr´existantes, dites classes de base directes, et d’un ensemble de membres sp´cifiques de la classe
               e                e e
nouvelle, appel´e alors classe d´riv´e. La syntaxe est :
                          e                  e                     e
         class classe : d´rivation classe, d´rivation classe, ... d´rivation classe {
              e               e                       e
             d´clarations et d´finitions des membres sp´cifiques de la nouvelle classe
         }
 u e                              e
o` d´rivation est un des mots-cl´s private, protected ou public (cf. section 4.2.2).
                                                          e e                                               oe
    Par exemple, voici une classe Tableau (tableau am´lior´ , en ce sens que la valeur de l’indice est contrˆl´e
                  e                                 `                           e
lors de chaque acc`s) et une classe Pile qui ajoute a la classe Tableau une donn´e membre exprimant le niveau
de remplissage de la pile et trois fonctions membres qui encapsulent le comportement particulier ( dernier
entr´ premier sorti ) des piles :
     e
         class Tableau {
             int *tab;
             int maxTab;
         public:
             int &operator[](int i) {
                      o
                 contrˆle de la valeur de i
                 return tab[i];
             }
             int taille() { return maxTab; }
             ...
         };




                                                      32
          class Pile : private Tableau {                      // Une Pile est un Tableau
              int niveau;                                     // avec des choses en plus
          public:
              void empiler(int) {
                  (*this)[niveau++] = x;
              }
              int depiler() {
                  return (*this)[--niveau];
              }
              int taille() { return niveau; }
              ...
          };
     ´         e
    Etant donn´e une classe C, une classe de base de C est soit une classe de base directe de C, soit une classe
de base directe d’une classe de base de C.
        e               e
    L’h´ritage est appel´ simple s’il y a une seule classe de base directe, il est dit multiple sinon. En C++,
   e            e
l’h´ritage peut ˆtre multiple.
                                        e                      e
    Encombrement. Dans la notion d’h´ritage il y a celle de r´union de membres. Ainsi, du point de vue de
                    e                                  e e
l’occupation de la m´moire, chaque objet de la classe d´riv´e contient un objet de la classe de base :

                                     tab                               tab
                                   maxTab                            maxTab
                                                                     niveau

                                 Fig. 4 – Un objet Tableau et un objet Pile

                                                e e
   Pour parler de l’ensemble des membres h´rit´s (par exemple, tab et maxTab) d’une classe de base B qui se
                             e e
trouvent dans une classe d´riv´e D on dit souvent le sous-objet B de l’objet D.
                                   e                                         e         e
   Visibilite. Pour la visibilit´ des membres (qui n’est pas l’accessibilit´, expliqu´e dans les sections sui-
               ´
                                       e e e                    e         e              e
vantes), il faut savoir que la classe d´riv´e d´termine une port´e imbriqu´e dans la port´e de la classe de base.
                                              e e
Ainsi, les noms des membres de la classe d´riv´e masquent les noms des membres de la classe de base.
   Ainsi, si unePile est un objet Pile, dans un appel comme
        unePile.taille()                 // la valeur de unePile.niveau
                                                                            a
la fonction taille() de la classe Tableau et celle de la classe Pile (c’est-`-dire les fonctions Tableau::taille()
                                           e                                           e
et Pile::taille()) ne sont pas en comp´tition pour la surcharge, car la deuxi`me rend tout simplement la
       e                 e           e                 e                  e     a                      e
premi`re invisible. L’op´rateur de r´solution de port´e permet de rem´dier ` ce masquage (sous r´serve qu’on
                  e
ait le droit d’acc`s au membre taille de la classe Tableau) :
        unePile.Tableau::taille()        // la valeur de unePile.nbr

4.2       e                      e
         H´ritage et accessibilit´ des membres
4.2.1                e e
         Membres prot´g´s
                                         e                                               e e           e
    En plus des membres publics et priv´s, une classe C peut avoir des membres prot´g´s. Annonc´s par le mot
  e                    e                        e        e
cl´ protected, ils repr´sentent une accessibilit´ interm´diaire car ils sont accessibles par les fonctions membres
                                                                                          e e
et amies de C et aussi par les fonctions membres et amies des classes directement d´riv´es de C.
                      e e
    Les membres prot´g´s sont donc des membres qui ne font pas partie de l’interface de la classe, mais dont
        e                   e          e                                                   e e
on a jug´ que le droit d’acc`s serait n´cessaire ou utile aux concepteurs des classes d´riv´es.
                                                                           e
   Imaginons, par exemple, qu’on veuille comptabiliser le nombre d’acc`s faits aux objets de nos classes
                                                                      `e                e
Tableau et Pile. Il faudra leur ajouter un compteur, qui n’aura pas a ˆtre public (ces d´comptes ne regardent
                                                    e
pas les utilisateurs de ces classes) mais qui devra ˆtre accessible aux membres de la classe Pile, si on veut
           e
que les acc`s aux piles qui ne mettent en œuvre aucun membre des tableaux, comme dans vide(), soient bien
      e
compt´s.



                                                       33
           class Tableau {
               int *tab;
               int maxTab;
           protected:
               int nbrAcces;
           public:
               Tableau(int t) {
                   nbrAcces = 0;
                   tab = new int[maxTab = t];
               }
               int &operator[](int i) {
                        o
                   contrˆle de la valeur de i
                   nbrAcces++;
                   return tab[i];
               }
           };
           class Pile : private Tableau {
               int niveau;
           public:
               Pile(int t)
                   : Tableau(t), niveau(0) { }
               bool vide() {
                   nbrAcces++; return niveau == 0;
               }
               void empiler(int x) {
                   (*this)[niveau++] = x;
               }
               int depiler() {
                   return (*this)[--niveau];
               }
           };

4.2.2      e           e      e e
          H´ritage priv´, prot´g´, public
                               e              e
    En choisissant quel mot-cl´ indique la d´rivation, parmi private, protected ou public, le programmeur
  e                     e                 e e
d´termine l’accessibilit´ dans la classe d´riv´e des membres de la classe de base. On notera que :
                                  e                           e                              e e
    – cela concerne l’accessibilit´ des membres, non leur pr´sence (les objets de la classe d´riv´e contiennent
      toujours tous les membres de toutes les classes de base),
           e                               `                         e
    – la d´rivation ne peut jamais servir a augmenter l’accessibilit´ d’un membre.
         e                                                  e e          e e
    Cons´quence de ces deux points, les objets des classes d´riv´es ont g´n´ralement des membres inaccessibles :
                  e                              e                                   e e
les membres priv´s de la classe de base sont pr´sents dans les objets de la classe d´riv´e, mais il n’y a aucun
                  ee
moyen d’y faire r´f´rence.
   Heritage prive. Syntaxe :
    ´           ´
        class classeD´riv´e : privateoptionnel classeDeBase { ... }
                     e e
               e                            e           e        e            e
    Le mot cl´ private est optionnel car l’h´ritage priv´ est l’h´ritage par d´faut. C’est la forme la plus
               e
restrictive d’h´ritage : voyez la figure 5.
             e           e                                          ıt
    Dans l’h´ritage priv´, l’interface de la classe de base disparaˆ (l’ensemble des membres publics cessent
  e                                                                e             e                          e e
d’ˆtre publics). Autrement dit, on utilise la classe de base pour r´aliser l’impl´mentation de la classe d´riv´e,
                  `e                                             e e
mais on s’oblige a ´crire une nouvelle interface pour la classe d´riv´e.
    Cela se voit dans l’exemple d´j` donn´ des classes Tableau et Pile (cf. §). Il s’agit d’h´ritage priv´, car les
                                  ea      e                                                  e           e
                                    e
tableaux ne fournissent que l’impl´mentation des piles, non leur comportement.




                                                        34
                                 publics
                 membres        protégés                                 privés          statut dans
               de la classe                                                              la classe
                   de base       privés                             inaccessibles        dérivée
                              inaccessibles

                                                       e           e
                                             Fig. 5 – H´ritage priv´


   Exemple d’emploi d’un tableau :
                                   e
       Tableau t(taille max souhait´e);
       ...
       t[i] = x;
       etc.
   Emploi d’une pile :
                                      e
          Pile p(taille max souhait´e);
          p[i] = x;                                                   e
                                                     // ERREUR : l’op´rateur [] est inaccessible
          p.empiler(x);                              // Oui
          ...
          cout << t[i];                                               e
                                                     // ERREUR : l’op´rateur [] est inaccessible
          cout << p.depiler();                       // Oui
          etc.
                      e              e      e
   A retenir : si D d´rive de mani`re priv´e de B alors un objet D est une sorte de B, mais les utilisateurs
                         `                                               `                          e
de ces classes n’ont pas a le savoir. Ou encore : ce qu’on peut demander a un B, on ne peut pas forc´ment le
            `
demander a un D.
   Heritage protege. Syntaxe :
    ´           ´ ´
                   e e
      class classeD´riv´e : protected classeDeBase { ... }
                    e                           e                                     ` e              e
    Cette forme d’h´ritage, moins souvent utilis´e que les deux autres, est similaire a l’h´ritage priv´ (la classe
                      e                         e e                                         e                e
de base fournit l’impl´mentation de la classe d´riv´e, non son interface) mais on consid`re ici que les d´tails
         e              a                                e e
de l’impl´mentation, c.-`-d. les membres publics et prot´g´s de la classe de base, doivent rester accessibles aux
              e                    e e                   e e
concepteurs d’´ventuelles classes d´riv´es de la classe d´riv´e.


                                 publics
                 membres        protégés                                protégés         statut dans
               de la classe                                                              la classe
                   de base       privés                             inaccessibles        dérivée
                              inaccessibles

                                                     e           e e
                                           Fig. 6 – H´ritage prot´g´

   Heritage public. Syntaxe :
    ´
                   e e
      class classeD´riv´e : public classeDeBase { ... }
             e                                                        e            ee
    Dans l’h´ritage public (voyez la figure 7) l’interface est conserv´ : tous les ´l´ments du comportement
                                                                       e e                      e
public de la classe de base font partie du comportement de la classe d´riv´e. C’est la forme d’h´ritage la plus
  e                 e                                     `                           e
fr´quemment utilis´e, car la plus utile et la plus facile a comprendre : dire que D d´rive publiquement de B
                           e                                                                              `
c’est dire que, vu de l’ext´rieur, tout D est une sorte de B, ou encore que tout ce qu’on peut demander a un
                               `
B on peut aussi le demander a un D.
                                  e                                               e
    La plupart des exemples d’h´ritage que nous examinerons seront des cas de d´rivation publique.


                                                        35
                                publics                                publics
                                                                                      statut dans
                membres        protégés                               protégés        la classe
              de la classe                                                            dérivée
                  de base       privés                            inaccessibles
                             inaccessibles

                                                     e
                                           Fig. 7 – H´ritage public


4.3       e
       Red´finition des fonctions membres
                           e                      e e          e
    Lorsqu’un membre sp´cifique d’une classe d´riv´e a le mˆme nom qu’un membre d’une classe de base, le
                                                           o
premier masque le second, et cela quels que soient les rˆles syntaxiques (constante, type, variable, fonction,
etc.) de ces membres.
                                       e
    La signification de ce masquage d´pend de la situation :
                                                                 e
    – il peut s’agir des noms de deux fonctions membres de mˆme signature,
                                                   e
    – il peut s’agir de fonctions de signatures diff´rentes, ou de membres dont l’un n’est pas une fonction.
                                              e                                                e e
   S’il ne s’agit pas de deux fonctions de mˆme signature, on a une situation maladroite, g´n´ratrice de
                                 e e           e e                                           e
confusion, dont on ne retire en g´n´ral aucun b´n´fice. En particulier, le masquage d’une donn´e membre par
                e                     e              e
une autre donn´e membre ne fait pas ´conomiser la m´moire, puisque les deux membres existent dans chaque
                    e e
objet de la classe d´riv´e.
    En revanche, le masquage d’une fonction membre de la classe de base par une fonction membre de mˆme     e
                    e                       e                          e
signature est une d´marche utile et justifi´e. On appelle cela une red´finition de la fonction membre.
                                                       e                                                 e
    La justification est la suivante : si la classe D d´rive publiquement de la classe B, tout D peut ˆtre vu
comme une sorte de B, c’est-`-dire un B am´lior´ (augment´ des membres d’autres classes de base, ou de
                              a                  e e             e
            e                                        e                e                         `
membres sp´cifiques) ; il est donc naturel qu’un D r´ponde aux requˆtes qu’on peut soumettre a un B, et qu’il
   e            c      e e         u      ee            e                   e e
y r´ponde de fa¸on am´lior´e. D’o` l’int´rˆt de la red´finition : la classe d´riv´e donne des versions analogues
mais enrichies des fonctions de la classe de base.
                                                                      e          e    a                    e e
    Souvent, cet enrichissement concerne le fait que les fonctions red´finies acc`dent ` ce que la classe d´riv´e
                                                                         e e       a            e
a de plus que la classe de base. Exemple : un Pixel est un point am´lior´ (c.-`-d. augment´ d’un membre
      e                                                     `
suppl´mentaire, sa couleur). L’affichage d’un pixel consiste a l’afficher en tant que point, avec des informations
                                    e
additionnelles. Le code suivant refl`te tout cela :
         class Point {
             int x, y;
         public:
             Point(int, int);
             void afficher() {                                        // affichage d’un Point
                 cout << ’(’ << x << ’,’ << y << ’)’;
             }
             ...
         };
         class Pixel : public Point {
             char *couleur;
         public:
             Pixel(int, int, char *);
             void afficher() {                                        // affichage d’un Pixel
                 cout << ’[’;
                 Point::afficher();                                   // affichage en tant que Point
                 cout << ’;’ << couleur << ’]’;
             }
             ...
         };


                                                      36
    Utilisation :
           Point pt(2, 3);
           Pixel px(4, 5, "rouge");
           ...
           pt.afficher();                                         // affichage obtenu : (2,3)
           px.afficher();                                         // affichage obtenu : [(4,5) ;rouge]


4.4       e                                 e e
        Cr´ation et destruction des objets d´riv´s
                                                          e e                e
   Lors de la construction d’un objet, ses sous-objets h´rit´s sont initialis´s selon les constructeurs des classes
de base14 correspondantes. S’il n’y a pas d’autre indication, il s’agit des constructeurs par d´faut.
                                                                                                 e
   Si des arguments sont requis, il faut les signaler dans le constructeur de l’objet selon une syntaxe voisine
                  a
de celle qui sert ` l’initialisation des objets membres (cf. section 2.5) :
                       e
         classe(param`tres)
                                             e
                   : classeDeBase(param`tres), ... classeDeBase(param`tres) { e
              corps du constructeur
         }
            e         e
   De la mˆme mani`re, lors de la destruction d’un objet, les destructeurs des classes de base directes sont
              e                 `e
toujours appel´s. Il n’y a rien a ´crire, cet appel est toujours implicite. Exemple :

           class Tableau {
               int *table;
               int nombre;
           public:
               Tableau(int n) {
                   table = new int[nombre = n];
               }
               ~Tableau() {
                   delete [] table;
               }
               ...
           };
           class Pile : private Tableau {
               int niveau;
           public:
               Pile(int max)
                       : Tableau(max) {
                   niveau = 0;
               }
               ~Pile() {
                   if (niveau > 0)
                       erreur("Destruction d’une pile non vide");
                   }                               // ceci est suivi d’un appel de ~Tableau()
               ...
           };

  14 Il s’agit ici, tout d’abord, des classes de base directes ; mais les constructeurs de ces classes de base directes appelleront ` leur
                                                                                                                                    a
tour les constructeurs de leurs classes de base directes, et ainsi de suite. En d´finitive, les constructeurs de toutes les classes de
                                                                                     e
               e e
base auront ´t´ appel´s.  e




                                                                   37
4.5      e                      e
        R´capitulation sur la cr´ation et destruction des objets
                 e             e                                                   e    e        e
   Ayant examin´ le cas de l’h´ritage nous pouvons donner ici, sous une forme r´sum´e, la totalit´ de ce qu’il
    `      a                                                                             e e
y a a dire ` propos de la construction et le destruction des objets dans le cas le plus g´n´ral.

4.5.1   Construction
  1. Sauf pour les objets qui sont les valeurs de variables globales, le constructeur d’un objet est appel´ e
          e               e
     imm´diatement apr`s l’obtention de l’espace pour l’objet.
                      e                                                  e                              e
     On peut consid´rer que l’espace pour les variables globales existe d`s que le programme est charg´. Les
                                                e                    e
     constructeurs de ces variables sont appel´s, dans l’ordre des d´clarations de ces variables, juste avant
     l’activation de la fonction principale du programme (en principe main).
  2. L’activation d’un constructeur commence par les appels des constructeurs de chacune de ses classes de
                                     e
     base directes, dans l’ordre de d´claration de ces classes de base directes.
               e                                                                           e
  3. Sont appel´s ensuite les constructeurs de chacun des objets membres, dans l’ordre de d´claration de ces
     objets membres.
                                                                          e e
  4. Enfin, les instructions qui composent le corps du constructeur sont ex´cut´es.
   Sauf indication contraire, les constructeurs dont il est question aux points 2 et 3 sont les constructeurs
                                                          e                                      e      e e
sans argument des classes de base et des classes des donn´es membres. Si des arguments doivent ˆtre pr´cis´s
(par exemple parce que certaines de ces classes n’ont pas de constructeur sans argument) cela se fait selon la
syntaxe :
         classe(parametres)
                    : nomDeMembreOuDeClasseDeBase(param`tres),    e
                       ...
                       nomDeMembreOuDeClasseDeBase(param`tres) {  e
              corps du constructeur
              }
   Attention. Notez bien que, lorsque les constructeurs des classes de base et des objets membres sont
                     e                                      e e                                  e
explicitement appel´s dans le constructeur de la classe d´riv´e, ces appels ne sont pas effectu´s dans l’ordre
 u                         e                  ee
o` le programmeur les a ´crits : comme il a ´t´ dit aux points 2 et 3 ci-dessus, les appels de ces constructeurs
                                      e
sont toujours faits dans l’ordre des d´clarations des classes de base et des objets membres.

4.5.2   Destruction
                                                                                        e a      e
    Grosso modo, le principe g´n´ral est celui-ci : les objets contemporains (attach´s ` un mˆme contexte,
                              e e
  e            e      e                      e                                     e               e
cr´es par une mˆme d´claration, etc.) sont d´truits dans l’ordre inverse de leur cr´ation. Par cons´quent
         e                                                  e
  1. L’ex´cution du destructeur d’un objet commence par l’ex´cution des instructions qui en composent le
     corps.
  2. Elle continue par les appels des destructeurs des classes des objets membres, dans l’ordre inverse de leurs
      e
     d´clarations.
                e                                                                                  e
  3. Sont appel´s ensuite les destructeurs des classes de base directes, dans l’ordre inverse des d´clarations
     de ces classes de base.
                         e                    ee
  4. Enfin, l’espace occup´ par l’objet est lib´r´.
    A quel moment les objets sont-ils detruits ? Les objets qui sont les valeurs de variables globales
                                                ´
       e           e                e
sont d´truits imm´diatement apr`s la terminaison de la fonction principale (en principe main), dans l’ordre
               e
inverse de la d´claration des variables globales.
                       ee       e                         e
    Les objets qui ont ´t´ allou´s dynamiquement ne sont d´truits que lors d’un appel de delete les concernant.
                                                                               e                       o
    Les objets qui sont des valeurs de variables automatiques (locales) sont d´truits lorsque le contrˆle quitte
                                          e                                                          e
le bloc auquel ces variables sont rattach´es ou, au plus tard, lorsque la fonction contenant leur d´finition se
termine.
    Objets temporaires. Les objets temporaires ont les vies les plus courtes possibles. En particulier, les
                       ee            e                                e                 e
objets temporaires cr´´s lors de l’´valuation d’une expression sont d´truits avant l’ex´cution de l’instruction
suivant celle qui contient l’expression.


                                                      38
                            ee                        ee                        a                              e
   Les objets temporaires cr´´s pour initialiser une r´f´rence persistent jusqu’` la destruction de cette derni`re.
Exemple :
         {
         Point &r = Point(1, 2);                                          ee
                                            // l’objet Point temporaire cr´´ ici vit autant que r,
                                                     a                     a
                                            // c’est-`-dire au moins jusqu’` la fin du bloc
         ...
         }
                                              e
   Attention, les pointeurs ne sont pas trait´s avec autant de soin. Il ne faut jamais initialiser un pointeur
                                          e
avec l’adresse d’un objet temporaire (l’op´rateur new est fait pour cela) :
         {
         Point *p = & Point(3, 4);                // ERREUR
         Point *q = new Point(5, 6);              // Oui
                                                               e            eaee e
         ici, le pointeur p est invalide : le point de coordonn´es (3,4) a d´j` ´t´ d´truit
         delete q;                                // Oui
         delete p;                                // ERREUR (pour plusieurs raisons)
         }


4.6     Polymorphisme
4.6.1   Conversion standard vers une classe de base
                    e
    Si la classe D d´rive publiquement de la classe B alors les membres de B sont membres de D. Autrement
                                        e            `
dit, les membres publics de B peuvent ˆtre atteints a travers un objet D. Ou encore : tous les services offerts
par un B sont offerts par un D.
              e        a u               e
    Par cons´quent, l` o` un B est pr´vu, on doit pouvoir mettre un D. C’est la raison pour laquelle la
conversion (explicite ou implicite) d’un objet de type D vers le type B, une classe de base accessible de D, est
 e
d´finie, et a le statut de conversion standard. Exemple :
         class Point {                              // point     e e
                                                                g´om´trique
             int x, y;
         public:
             Point(int, int);
             ...
             };
         class Pixel : public Point {                                     e
                                                    // pixel = point color´
             char *couleur;
         public:
             Pixel(int, int, char *);
             ...
             };
   Utilisation :
         Pixel px(1, 2, "rouge");
         Point pt = px;                                           ee       a u            e
                                                    // un pixel a ´t´ mis l` o` un point ´tait
                                                    // attendu : il y a conversion implicite

             e                                                       e
    De mani`re interne, la conversion d’un D vers un B est trait´e comme l’appel d’une fonction membre de
                            e e           e                                        e
D qui serait publique, prot´g´e ou priv´e selon le mode (ici : public) dont D d´rive de B.
                                         e e                                e           e e
    Ainsi, la conversion d’une classe d´riv´e vers une classe de base priv´e ou prot´g´e existe mais n’est pas
                                    e                    e e                                     e
utilisable ailleurs que depuis l’int´rieur de la classe d´riv´e. Bien entendu, le cas le plus int´ressant est celui
       e                                                                                              e
de la d´rivation publique. Sauf mention contraire, dans la suite de cette section nous supposerons ˆtre dans ce
cas.




                                                        39
                             `                 `                        ee
   Selon qu’elle s’applique a des objets ou a des pointeurs (ou des r´f´rences) sur des objets, la conversion
                         e e                                          e e       e     e
d’un objet de la classe d´riv´e vers la classe de base recouvre deux r´alit´s tr`s diff´rentes :
   1. Convertir un objet D vers le type B c’est lui enlever tous les membres qui ne font pas partie de B (les
           e                e e
membres sp´cifiques et ceux h´rit´s d’autres classes). Dans cette conversion il y a perte effective d’information :
voyez la figure 8.


                                 oD        B

                                                             D
                             (B)oD         B


                            Fig. 8 – Conversion de oD, un objet D, en un objet B

                                                       a
    2. Au contraire, convertir un D* en un B * (c’est-`-dire un pointeur sur D en un pointeur sur B ) ne fait
                                   ea
perdre aucune information. Point´ ` travers une expression de type B *, l’objet n’offre que les services de la
                                 e
classe B, mais il ne cesse pas d’ˆtre un D avec tous ses membres : voyez la figure 9.

                            pD             (B*)pD


                                            B

                                                             D

                 Fig. 9 – Conversion de pD, un pointeur sur un D, en un pointeur sur un B

                              e
   Par exemple, supposons poss´der deux fonctions de prototypes :
         void fon1(Point pt);
         void fon2(Point *pt);
                                e             e
et supposons qu’elles sont appel´es de la mani`re suivante :
         Pixel pix = Pixel(2, 3, "rouge")
         ...
         fon1(pix);
         fon2(&pix);
                                          e                                   `         e                  e
Le statut de l’objet pix en tant que param`tre de ces deux fonctions est tout a fait diff´rent. L’objet pass´
a                                             e                                    ea
` fon1 comme argument est une version tronqu´e de pix, alors que le pointeur pass´ ` fon2 est l’adresse de
l’objet pix tout entier :
         void fon1(Point pt) {
                                                                                   e
             La valeur de pt est un Point ; il n’a pas de couleur. Il y avait peut-ˆtre
             ` l’origine un Pixel mais, ici, aucune conversion (autre que d´finie
             a                                                                e
                                                       a
             par l’utilisateur) ne peut faire un Pixel ` partir de pt.
         }
         void fon2(Point *pt) {
                                e`                                 `
             Il n’est rien arriv´ a l’objet dont l’adresse a servi a initialiser pt lors de
             l’appel de cette fonction. Si on peut garantir que cet objet est un Pixel,
                                          e                       e
             l’expression suivante (plac´e sous la responsabilit´ du programmeur) a
             un sens :
                             ((Pixel *) pt)->couleur ...
         }



                                                        40
                                      ee                    ee          e
    Pour ce qui nous occupe ici, les r´f´rences (pointeurs g´r´s de mani`re interne par le compilateur) doivent
e          ee                                                                e
ˆtre consid´r´es comme des pointeurs. Nous pouvons donc ajouter un troisi`me cas :
          void fon3(Point &rf) {
                                 e`                      `
              Il n’est rien arriv´ a l’objet qui a servi a initialiser rf lors de l’appel de
              cette fonction. Si on peut garantir que cet objet est un Pixel, l’expression
                             e                        e
              suivante (plac´e sous la responsabilit´ du programmeur) a un sens :
                              ((Pixel &) rf).couleur ...
          }


4.6.2                                    e e
         Type statique, type dynamique, g´n´ralisation
          e
    Consid´rons la situation suivante :

          class B {
              ...
          };
          class D : public B {
              ...
          };
          D unD;
          ...
          B unB = unD;
          B *ptrB = &unD;
          B &refB = unD;
                                          e
    Les trois affectations ci-dessus sont l´gitimes ; elles font jouer la conversion standard d’une classe vers une
                                       e
classe de base. Le type de unB ne soul`ve aucune question (c’est un B), mais les choses sont moins claires pour
les types de ptrB et refB. Ainsi ptrB, par exemple, pointe-t-il un B ou un D ?
    – on dit que le type statique de *ptrB (ce que ptB pointe) et de refB est B, car c’est ainsi que ptB et refB
          ee e       e
      ont ´t´ d´clar´es ;
    – on dit que le type dynamique de *ptrB et de refB est D, car tel est le type des valeurs effectives de ces
      variables.
                                       e                                                        `
    Le type statique d’une expression d´coule de l’analyse du texte du programme ; il est connu a la compilation.
                                        e       e
Le type dynamique, au contraire, est d´termin´ par la valeur courante de l’expression, il peut changer durant
    e
l’ex´cution du programme.
   La notion de type dynamique va nous amener assez loin. Pour commencer, remarquons ceci : grˆce aux   a
                                        ee                               ee
conversions implicites de pointeurs et r´f´rences vers des pointeurs et r´f´rences sur des classes de base, tout
           e               e                        e e                                               e e
objet peut ˆtre momentan´ment tenu pour plus g´n´ral qu’il n’est. Cela permet les traitements g´n´riques,
                                       e                                  c           e
comme dans l’exemple suivant, qui pr´sente un ensemble de classes con¸ues pour g´rer le stock d’un super-
march´15 :
      e

          struct Article {
              char *denom;                                               // denomination
              int quant;                                                           e
                                                                         // quantit´ disponible
              double prix;                                               // prix HT
              Article(char *d, int q, double p)
                  : denom(d), quant(q), prix(p) { }
              ...
          };

  15 Nous d´clarons ici des structures (une structure est une classe dont les membres sont par d´faut publiques) pour ne pas nous
           e                                                                                    e
                          e
encombrer avec des probl`mes de droits d’acc`s.e




                                                               41
         struct Perissable : public Article {
             Date limite;
             Perissable(char *d, int q, double p, Date l)
                 : Article(d, q, p), limite(l) { }
             ...
         };
         struct Habillement : public Article {
             int taille;
             Habillement(char *d, int q, double p, int t)
                 : Article(d, q, p), taille(t) { }
             ...
         };
         struct Informatique : public Article {
             char garantie;
             Informatique (char *d, int q, double p, char g)
                 : Article(d, q, p), garantie(g) { }
             ...
         };

                                           e                                     e
   Introduisons quelques structures de donn´es (des tableaux de pointeurs) pour m´moriser les diverses classes
             e
d’articles pr´sents dans le stock :
          Perissable      *aliment[maxAliment];
          Habillement *vetemts[maxVetemts];
          Informatique *info[maxInfo];
          etc.
    e         e                             e
   D´finissons ´galement une structure pour m´moriser tous les articles du stock :
         Article *stock[maxStock];
                                                                                                e
   Initialisons tout cela (notez que chaque adresse fournie par un appel de new est d’abord copi´e dans un
           e                   e e    e         e                   e e
tableau sp´cifique et ensuite g´n´ralis´e et copi´e dans le tableau g´n´rique) :
         nStock = nAliment = nVetemts = nInfo = 0;
         stock[nStock++] = aliment[nAliment++] =
                     new Perissable("Foie gras Shell 1Kg", 1000, 450.0, Date(15,1,2000));
         stock[nStock++] = vetemts[nVetemts] =
                     new Habillement("Tenue gendarme fluo rose", 250, 200.0, 52);
         stock[nStock++] = info[nInfo++] =
                     new Informatique("Pentium III 800 Mhz Diesel HDI", 500, 1000.0, ’C’);
         etc.
                ee
   En tant qu’´l´ments du tableau stock, les objets des classes Perissable, Habillement, etc., sont tenus
           e e                                  e                                        e         e e
pour plus g´n´raux qu’ils ne sont, ce qui est tr`s commode pour faire sur chacun une op´ration g´n´rique. Par
                                                                     e      e
exemple, le calcul de la valeur totale du stock n’a pas besoin des sp´cificit´s de chaque article :
         double valeurStock = 0;
         for (int i = 0; i < nStock; i++)
             valeurStock += stock[i]->quant * stock[i]->prix;
                                        e    e e                                 e     a
   Dans cet exemple nous avons utilis´ la g´n´ralisation des pointeurs pour acc´der ` des membres dont les
         e                                               e                                          e
objets h´ritent sans les modifier : les notions de quantit´ disponible et de prix unitaire sont les mˆmes pour
toutes les sortes d’articles du stock.
                                          e                     e a
    La question devient beaucoup plus int´ressante lorsqu’on acc`de ` des membres de la classe de base qui
          e                      e e                                                                    e a
sont red´finis dans les classes d´riv´es. Voici un autre exemple, classique : la classe Figure est destin´e `
     e           ee                a                               e e
repr´senter les ´l´ments communs ` tout un ensemble de figures g´om´triques, telles que des triangles, des
ellipses, des rectangles, etc. :


                                                     42
         class Figure {
             ...
         };
         class Triangle : public Figure {
             ...
         };
         class Ellipse : public Figure {
             ...
         };
         Figure *image[maxFigure];
         int n = 0;
         image[n++] = new Triangle(...);
         image[n++] = new Figure(...);
         image[n++] = new Ellipse(...);
         ...
         for (int i = 0; i < n; i++)
             image[i]->seDessiner();

                                                          e             e     e   e
    Si on suppose que la fonction seDessiner (qui doit n´cessairement ˆtre d´clar´e dans la classe Figure, sinon
                               e             a                        e
la ligne ci-dessus serait trouv´e incorrecte ` la compilation) est red´finie dans les classes Triangle, Ellipse,
                e                                                                              e
etc., il est int´ressant de se demander quelle est la fonction qui sera effectivement appel´e par l’expression
                             e
image[i]->seDessiner() ´crite ci-dessus.
           e         e                            e ea                                     e e
    Par d´faut, la d´termination du membre acc´d´ ` travers une expression comme la pr´c´dente se fait durant
                       a           e                                        ee
la compilation, c’est-`-dire d’apr`s le type statique du pointeur ou de la r´f´rence. Ainsi, dans l’expression
         image[i]->seDessiner();
c’est la fonction seDessiner de la classe Figure (probablement une fonction bouche-trou) qui est appel´e.     e
        e e e             e a             e                  e                  e
Cette r´alit´ d´cevante ob´it ` des consid´rations d’efficacit´ et de compatibilit´ avec le langage C ; elle semble
               e              ee          e e
limiter consid´rablement l’int´rˆt de la g´n´ralisation des pointeurs...
                                          e                                       e
   ...heureusement, la section suivante pr´sente un comportement beaucoup plus int´ressant !

4.7    Fonctions virtuelles
                                                                                   e
    Soit f une fonction membre d’une classe C. Si les conditions suivantes sont r´unies :
               e                        e e
    – f est red´finie dans des classes d´riv´es (directement ou indirectement) de C,
                         e a                                ee                                             e e
    – f est souvent appel´e ` travers des pointeurs ou des r´f´rences sur des objets de C ou de classes d´riv´es
      de C,
          e      e                                                         e e        e                     e
alors f m´rite d’ˆtre une fonction virtuelle. On exprime cela en faisant pr´c´der sa d´claration du mot r´serv´  e
                                                                  e a                                 ee
virtual. L’important service obtenu est celui-ci : si f est appel´e ` travers un pointeur ou une r´f´rence sur
                                                         e                         e                            e
un objet de C, le choix de la fonction effectivement activ´e, parmi les diverses red´finitions de f, se fera d’apr`s
le type dynamique de cet objet.
                   e
    Une classe poss´dant des fonctions virtuelles est dite classe polymorphe. La qualification virtual devant
      e                                                          e
la red´finition d’une fonction virtuelle est facultative : les red´finitions d’une fonction virtuelle sont virtuelles
d’office.
    Exemple :
         class Figure {
         public:
             virtual void seDessiner() {
                                                                    e e
                 fonction passe-partout, probablement sans grand int´rˆt
             }
             ...
         };



                                                        43
         class Triangle : public Figure {
         public:
             void seDessiner() {
                     e                 e
                 impl´mentation du trac´ d’un triangle
             }
             ...
         };
         class Ellipse : public Figure {
         public:
             void seDessiner() {
                     e                 e
                 impl´mentation du trac´ d’une ellipse
             ...
         };
         Figure *image[maxFigure];
         int n = 0;
         image[n++] = new Triangle(...);
         image[n++] = new Figure(...);
         image[n++] = new Ellipse(...);
         ...
         for (int i = 0; i < N ; i++)
             image[i]->seDessiner();
                                                                       e
   Maintenant, l’appel image[i]->seDessiner() active la fonction red´finie dans la classe de l’objet effecti-
             e                                                               e
vement point´ par image[i]. Cela constitue, bien entendu, le comportement int´ressant, celui qu’on recherche
                                                  e                                        ee e       e
presque toujours. On notera que cet appel est l´gal parce que la fonction seDessiner a ´t´ d´clar´e une
     e
premi`re fois dans la classe qui correspond au type statique de image[i].
        e     e e                      e                                e
   En r`gle g´n´rale, les diverses red´finitions d’une fonction peuvent ˆtre vues comme des versions de plus en
       e      e                       e e           e   e e
plus sp´cialis´es d’un traitement sp´cifi´ de mani`re g´n´rique dans la classe de base C. Dans ces conditions,
                                  a                                                                  e
appeler une fonction virtuelle f ` travers un pointeur p sur un objet de C c’est demander l’ex´cution de la
                       e      e                 `                         `            u               e e
version de f la plus sp´cialis´e qui s’applique a ce que p pointe en fait a l’instant o` l’appel est ex´cut´.
                                             e                             e
   Contraintes En concurrence avec le m´canisme du masquage, la red´finition des fonctions virtuelles subit
                                               e                    e
des contraintes fortes, sur les types des param`tres et le type du r´sultat :
                                                                                  e                 e
   1. Tout d’abord, la signature (liste des types des arguments, sans le type du r´sultat) de la red´finition
     e                    e                         e    e                      e    e
doit ˆtre strictement la mˆme que celle de la premi`re d´finition, sinon la deuxi`me d´finition n’est pas une
   e                                                    e
red´finition mais un masquage pur et simple de la premi`re.
                            e
   2. La contrainte sur le r´sultat garantit que l’appel d’une fonction virtuelle est correct et sans perte d’in-
formation quoi qu’il arrive. Supposons que f soit une fonction virtuelle d’une classe C rendant un r´sultat
                                                                                                          e
de type T1 , et qu’une red´finition de f dans une classe d´riv´e de C rende un r´sultat de type T2 . Durant la
                          e                               e e                     e
compilation, une expression telle que (p est de type C*) :
      p->f()
est vue comme de type T1 ; le compilateur v´rifiera que ce type correspond ` ce que le contexte de cette
                                                e                                 a
expression requiert. Or, a l’ex´cution, cette expression peut renvoyer un objet de type T2 . Pour que cela soit
                         `     e
coh´rent et efficace, il faut que le type T2 puisse ˆtre converti dans le type T1 , et cela sans avoir ` modifier
    e                                               e                                                a
                            e
(tronquer) la valeur renvoy´e. Il faut donc :
    – soit T1 = T2 ,
    – soit T1 est un pointeur [resp. une r´f´rence] sur un objet d’une classe C1 , T2 est un pointeur [resp. une
                                          ee
      r´f´rence] sur un objet d’une classe C2 et C1 est une classe de base accessible de C2 .
       ee
    Autrement dit, si une fonction virtuelle f rend l’adresse d’un T , toute red´finition de f doit rendre l’adresse
                                                                                 e
d’une sorte de T ; si f rend une r´f´rence sur un T , toute red´finition de f doit rendre une r´f´rence sur une
                                   ee                            e                                ee
sorte de T ; enfin, si f ne rend ni un pointeur ni une r´f´rence, toute red´finition de f doit rendre la mˆme
                                                          ee                   e                               e
chose que f .


                                                        44
   Exemple :

         class Figure {
         public:
             virtual Figure *symetrique(Droite *d);
             ...
         };
         class Triangle : public Figure {
         public:
             Triangle *symetrique(Droite *d);
             ...
         };

                                                          e
   Exemple d’utilisation (notez que la contrainte sur le r´sultat assure la correction de l’initialisation de f2,
quel que soit le type dynamique de f1) :
                                         e e
         Figure *f1 = new nomDuneClasseD´riv´eDeFigure(...);
         Figure *f2 = f1->symetrique(axe);

                                   e                  e            e
    Les deux contraintes indiqu´es sont examin´es une apr`s l’autre : si la contrainte sur la signature (ici
                                                e                    e
(Droite *)) est satisfaite, le compilateur d´tecte un cas de red´finition de fonction virtuelle et, alors seulement,
    e                           e                                                 e
il v´rifie la contrainte sur le r´sultat (ici la contrainte est satisfaite car le r´sultat est un pointeur dans les deux
                                                                                     e
cas et Figure est une classe de base accessible de Triangle). Si cette derni`re n’est pas satisfaite, il annonce
une erreur.

4.8    Classes abstraites
                                                                           e a                  e e
    Les fonctions virtuelles sont souvent introduites dans des classes plac´es ` des niveaux si ´lev´s de la
  e             e                                               e                  e
hi´rarchie (d’h´ritage) qu’on ne peut pas leur donner une impl´mentation utile ni mˆme vraisemblable.
               e                    `
    Une premi`re solution consiste a en faire des fonctions vides :
         class Figure {
         public:
             virtual void seDessiner() {
             }                      e                       e e
                           // sera d´finie dans les classes d´riv´es
             ...
         };
                                             e
   En fait, si la fonction seDessiner peut ˆtre vide c’est parce que :
   – la classe Figure ne doit pas avoir d’objets,
                                               e e
   – la classe Figure doit avoir des classes d´riv´es (qui, elles, auront des objets),
                                   e       e                        e e
   – la fonction seDessiner doit ˆtre red´finie dans les classes d´riv´es.
                                                                         e
    Bref, la classe Figure est une abstraction. Un programme ne cr´era pas d’objets Figure, mais des objets
                                        e e                      o
Rectangle, Ellipse, etc., de classes d´riv´es de Figure. Le rˆle de la classe Figure n’est pas de produire des
                     e
objets, mais de repr´senter ce que les objets rectangles, ellipses, etc., ont en commun ; or, s’il y a une chose que
                                               e
ces objets ont en commun, c’est bien la facult´ de se dessiner. Nous dirons que Figure est une classe abstraite.
    On remarque que la fonction seDessiner introduite au niveau de la classe Figure n’est pas un service
                                                e e                                             o
rendu aux programmeurs des futures classes d´riv´es de Figure, mais une contrainte : son rˆle n’est pas de
                                                                          e e                a
dire ce qu’est le dessin d’une figure, mais d’obliger les futures classes d´riv´es de Figure ` le dire. On peut
                                                               e                                              e
traduire cet aspect de la chose en faisant que cette fonction d´clenche une erreur. En effet, si elle est appel´e,
                          ee                                                ee                          e e
c’est que ou bien on a cr´´ un objet de la classe Figure, ou bien on a cr´´ un objet d’une classe d´riv´e de
                                     e
Figure dans laquelle on n’a pas red´fini la fonction seDessiner :




                                                          45
         class Figure {
         public:
             virtual void seDessiner() {
                                   e     e
                 erreur("on a oubli´ de d´finir la fonction seDessiner");
             }
             ...
         };

               e                                                   e      e     a     e
    Cette mani`re de faire est peu pratique, car l’erreur ne sera d´clench´e qu’` l’ex´cution (c’est le program-
                                                                   e
meur qui a commis l’oubli mais c’est l’utilisateur qui se fera r´primander !). En C++ on a une meilleure
            e
solution : d´finir une fonction virtuelle pure, par la syntaxe :

         class Figure {
         public:
             virtual void seDessiner() = 0;                      // Une fonction virtuelle pure
             ...
         };
                                                                        `                 e             e
    D’une part, cela officialise le fait que la fonction ne peut pas, a ce niveau, poss´der une impl´mentation.
                       e                                             e                  e
D’autre part, cela cr´e, pour le programmeur, l’obligation de d´finir cette impl´mentation dans une classe
 e e       e                        e                                           ee
d´riv´e ult´rieure, et permet de v´rifier pendant la compilation que cela a ´t´ fait. Une fonction virtuelle pure
                                       e e                                                           e
reste virtuelle pure dans les classes d´riv´es, aussi longtemps qu’elle ne fait pas l’objet d’une red´finition (autre
que = 0 ).
                                                                                                              e
   Pour le compilateur, une classe abstraite est une classe qui a des fonctions virtuelles pures. Tenter de cr´er
des objets d’une classe abstraite est une erreur, qu’il signale :
         class Figure {
         public:
             virtual void seDessiner() = 0;
             ...
             };

         class Rectangle : public Figure {
             int x1, y1, x2, y2;
         public:
             void seDessiner() {
                 trait(x1, y1, x2, y1);
                 trait(x2, y1, x2, y2);
                 trait(x2, y2, x1, y2);
                 trait(x1, y2, x1, y1);
             }
             ...
         };
   Utilisation :
         Figure f;                             //                   e
                                                    ERREUR : Cr´ation d’une instance de la
                                               //                classe abstraite Figure
         Figure *pt;                           //   Oui : c’est un pointeur
         pt = new Figure;                      //                   e
                                                    ERREUR : Cr´ation d’une instance de la
                                               //                classe abstraite Figure
         Rectangle r;                          //   Oui : la classe Rectangle n’est pas abstraite
         pt = new Rectangle;                   //   Oui




                                                        46
4.9      Identification dynamique du type
          u                                    e
    Le coˆt du polymorphisme, en espace m´moire, est d’un pointeur par objet, quel que soit le nombre de
fonctions virtuelles de la classe16 . Chaque objet d’une classe polymorphe comporte un membre de plus que
ceux que le programmeur voit : un pointeur vers une table qui donne les adresses qu’ont les fonctions virtuelles
pour les objets de la classe en question.
        u     e                                                                          u       e
    D’o` l’id´e de profiter de l’existence de ce pointeur pour ajouter au langage, sans coˆt suppl´mentaire, une
                                                    e                                       `
gestion des types dynamiques qu’il faudrait sinon ´crire au coup par coup (probablement a l’aide de fonctions
virtuelles). Fondamentalement, ce m´canisme se compose des deux op´rateurs dynamic cast et typeid.
                                       e                                e

4.9.1         e
          L’op´rateur dynamic cast
   Syntaxe
        dynamic_cast<type>(expression)
    Il s’agit d’effectuer la conversion d’une expression d’un type pointeur vers un autre type pointeur, les types
      e e
point´s ´tant deux classes polymorphes dont l’une, B, est une classe de base accessible de l’autre, D.
    S’il s’agit de g´n´ralisation (conversion dans le sens D* → B *), cet op´rateur ne fait rien de plus que la
                    e e                                                       e
conversion standard.
    Le cas int´ressant est B * → D*, conversion qui n’a de sens que si l’objet point´ par l’expression de type
                e                                                                     e
              e e                                            e e
B * est en r´alit´ un objet de la classe D ou d’une classe d´riv´e de D.
    L’op´rateur dynamic cast fait ce travail de mani`re sˆre et portable, et rend l’adresse de l’objet lorsque
          e                                             e   u
c’est possible, 0 dans le cas contraire :
           class Animal {
               ...
               virtual void uneFonction() {                       // il faut au moins une fonction virtuelle
               ...                                                // (car il faut des classes polymorphes)
               }
           };
           class Mammifere : public Animal {
               ...
           };
           class Chien : public Mammifere {
               ...
           };
           class Caniche : public Chien {
               ...
           };
           class Chat : public Mammifere {
               ...
           };
           class Reverbere {
               ...
           };
           Chien medor;
           Animal *ptr = &medor;
           ...

 16 En                u                                                       e
        temps, le coˆt du polymorphisme est celui d’une indirection suppl´mentaire pour chaque appel d’une fonction virtuelle,
puisqu’il faut aller chercher dans une table l’adresse effective de la fonction.




                                                             47
           Mammifere *p0 = ptr;
                                    a                                          e
                       // ERREUR (` la compilation) : un Animal n’est pas forc´ment un Mammif`re
                                                                                             e
           Mammifere *p1 = dynamic_cast<Mammifere *>(ptr);
                                    c                             e                  e
                       // OK : p1 re¸oit une bonne adresse, car M´dor est un mammif`re
           Caniche *p2 = dynamic_cast<Caniche *>(ptr);
                                         c           e
                       // OK, mais p2 re¸oit 0, car M´dor n’est pas un caniche
           Chat *p3 = dynamic_cast<Chat *>(ptr);
                                         c           e
                       // OK, mais p3 re¸oit 0, car M´dor n’est pas un chat non plus
           Reverbere *p4 = dynamic_cast<Reverbere *>(ptr);
                                         c           e                       e
                       // OK, mais p4 re¸oit 0, car M´dor n’est pas un reverb`re
       e                                   e               ee                          e
   L’op´rateur dynamic_cast s’applique ´galement aux r´f´rences. L’explication est la mˆme, sauf qu’en cas
d’impossibilit´ d’effectuer la conversion, cet op´rateur lance l’exception bad cast :
              e                                 e
           Animal &ref = medor;
           Mammifere &r1 = dynamic_cast<Mammifere &>(ref);             // Oui
           Caniche &r2 = dynamic_cast<Caniche &>(ref);                                              e
                                                                       // exception bad cast lanc´e ;
                                                                                    e
                                                                       // non attrap´e, elle est fatale

4.9.2         e
          L’op´rateur typeid
     Syntaxes :
        typeid(type)
ou
        typeid(expression)
       e                                                 u                                        e
   Le r´sultat est une valeur de type const type_info&, o` type_info est une classe de la biblioth`que
standard comportant au moins les membres suivants :
           class type_info {
           public:
               const char *name() const;
               int operator==(const type_info&) const;
               int operator!=(const type_info&) const;
               int before(const type_info&) const;
               ...
           };
                            e                                                    e
     On a donc la possibilit´ de comparer les types dynamiques et d’en obtenir l’´criture des noms. Exemple :
           Animal *ptr = new Caniche;
           cout << typeid(ptr).name() << ’\n’;
           cout << typeid(*ptr).name() << ’\n’;
                                  e
           cout << "L’animal point´ par ptr "
               << (typeid(*ptr) == typeid(Chien) ? "est" : "n’est pas")
               << " un chien\n";
                                  e
           cout << "L’animal point´ par ptr est un "
               << typeid(*ptr).name() << "\n";
affichage obtenu :
           Animal *
           Chien
                         e
           L’animal point´ par ptr n’est pas un chien
                         e
           L’animal point´ par ptr est un Caniche




                                                      48
5        e
      Mod`les (templates)
            e    e                                                      e e
    Un mod`le d´finit une famille de fonctions ou de classes param´tr´e par une liste d’identificateurs qui
    e                                                         e
repr´sentent des valeurs et des types. Les valeurs sont indiqu´es par leurs types respectifs, les types par le mot
 e    e                       e e               e    e
r´serv´ class. Le tout est pr´fix´ par le mot r´serv´ template. Exemple :
           template<class T, class Q, int N>
                             e                 e
               ici figure la d´claration ou la d´finition d’une fonction ou d’une classe
               dans laquelle T et Q apparaissent comme types et N comme constante
                                                 e
   Le mot class ne signifie pas que T et Q sont n´cessairement des classes, mais des types. Depuis la norme
            e                          e    e
ISO on peut ´galement utiliser le mot r´serv´ typename, qui est certainement un terme plus heureux.
   La production effective d’un ´l´ment de la famille d´finie par un mod`le s’appelle l’instanciation 17 du
                                  ee                       e                  e
    e                     e                  e             e
mod`le. Lors de cette op´ration, les param`tres qui repr´sentent des types doivent recevoir pour valeur des
                        e
types ; les autres param`tres doivent recevoir des expressions dont la valeur est connue durant la compilation.

5.1        e
        Mod`les de fonctions
                              e                  e e        e           ea
  Il n’y a pas en C++ un op´rateur ou un mot r´serv´ express´ment destin´ ` produire des instances des
   e                             e
mod`les. L’instanciation d’un mod`le de fonction
           template<param 1 , ... param k >
               type nom ( argfor 1 , ... argfor n )...
               e                                                      e    e                        e
est command´e implicitement par l’appel d’une des fonctions que le mod`le d´finit. Un tel appel peut ˆtre
e
´crit sous la forme :
           nom <arg 1 , ... arg k > (argeff 1 , ... argeff n )
dans laquelle les derniers arguments du mod`le, arg i ... arg k , ´ventuellement tous, peuvent ˆtre omis si leurs
                                            e                     e                            e
valeurs peuvent ˆtre d´duites sans ambigu¨ e des arguments effectifs de la fonction, argeff i , ... argeff n .
                 e     e                 ıt´
              e                e
   Exemple : d´claration du mod`le               recherche de la valeur minimale d’un tableau (ayant au moins un
´l´ment) :
ee
          template<class T> T min(T tab[], int n) {
              T min = tab[0];
              for (int i = 1; i < n; i++)
                   if (tab[i] < min) min = tab[i];
              return min;
          }
                                                                      e
    Voici un programme qui produit et utilise deux instances de ce mod`le :
           int t[] = { 10, 5, 8, 14, 20, 3, 19, 7 };
           ...
           cout << min<int>(t, 8);                                               // T = int
           cout << min<char>("BKEFYFFLKRNFAJDQKXJD", 20);                        // T = char
                                               e            e                                 e
    Puisque dans les deux cas l’argument du mod`le peut se d´duire de l’appel, ce programme s’´crit aussi :
           cout << min(t, 8);                                                    // T = int
           cout << min("BKEFYFFLKRNFAJDQKXJD", 20);                              // T = char
    ♣ Un mod`le de fonction peut coexister avec une ou plusieurs sp´cialisations. Par exemple, la fonction
                e                                                     e
                    e                 e                                                            e        a
suivante est une sp´cialisation du mod`le min qu’on ne peut pas obtenir par instanciation de ce mod`le, car `
                 e
la place de l’op´rateur < figure un appel de la fonction strcmp :
  17 Le choix du mot n’est pas tr`s heureux. Dans tous les autres langages ` objets, l’instanciation est l’op´ration de cr´ation d’un
                                 e                                         a                                 e            e
                                                                                                                               e
objet, et on dit qu’un objet est instance de sa classe. En C++ le mot instanciation ne s’utilise qu’en rapport avec les mod`les.




                                                                 49
         char *min(char *tab[], int n) {
              char *min = tab[0];
              for (int i = 1; i < n; i++)
                    if (strcmp(tab[i], min) < 0) min = tab[i];
              return min;
         }
              e                                                              e
   Pour le m´canisme de la surcharge des fonctions, les instances de mod`les sont souvent en concurrence
                                                                                                        eee
avec d’autres instances ou des fonctions ordinaires. Il faut savoir que les fonctions ordinaires sont pr´f´r´es
                       e                         e            e     e         eee                          e
aux instances de mod`les et les instances de mod`les plus sp´cialis´s sont pr´f´r´es aux instances de mod`les
         e      e
moins sp´cialis´s.
                                                                    e          e e        e
   Par exemple, donnons-nous un programme un peu plus compliqu´ que le pr´c´dent, en ´crivant une fonction
                                                                e
qui recherche l’indice du minimum d’un tableau, avec un deuxi`me tableau pour crit`re secondaire . Nous
                                                                                        e
 e
d´finissons :
              e                   e
   1. Un mod`le avec deux param`tres-types :
         template<class T1, class T2>
             int imin(T1 ta[], T2 tb[], int n) {
                 cout << "imin(T1 [], T2 [], int) : ";
                 int m = 0;
                 for (int i = 1; i < n; i++)
                     if (ta[i] < ta[m] || ta[i] == ta[m] && tb[i] < tb[m])
                         m = i;
                 return m;
             }
              e                   e                                                              ı
   2. Un mod`le qui en est une sp´cialisation partielle (le premier tableau est un tableau de chaˆnes, il n’y a
                     e
donc plus qu’un param`tre-type) :
         template<class T>
             int imin(char *ta[], T tb[], int n) {
                 cout << "imin(char *[], T [], int) : ";
                 int m = 0, r;
                 for (int i = 1; i < n; i++) {
                     r = strcmp(ta[i], ta[m]);
                     if (r < 0 || r == 0 && tb[i] < tb[m])
                         m = i;
                     }
                 return m;
             }
                                 e                 e         e                        ınes, plus aucun
   3. Une fonction qui est une sp´cialisation compl`te du mod`le (deux tableaux de chaˆ
      e
param`tre-type) :
        int imin(char *ta[], char *tb[], int n) {
             cout << "imin(char *[], char *[], int) : ";
             int m = 0, r;
             for (int i = 1; i < n; i++) {
                  r = strcmp(ta[i], ta[m]);
                  if (r < 0 || r == 0 && strcmp(tb[i], tb[m]) < 0)
                        m = i;
                  }
             return m;
        }
   Essayons tout cela :




                                                      50
         int ti1[5] = { 10, 5, 15, 5, 18 };
         int ti2[5] = { 20, 8, 20, 10, 20 };
         char *ts1[5] = { "Andre", "Denis", "Charles", "Andre", "Bernard" };
         char *ts2[5] = { "Duchamp", "Dubois", "Dumont", "Dupont", "Durand" };
         cout << imin(ti1, ti2, 5) << ’\n’;
         cout << imin(ts1, ti2, 5) << ’\n’;
         cout << imin(ts1, ts2, 5) << ’\n’;
   L’affichage obtenu :
         imin(T1 [], T2 [], int) : 1
         imin(char *[], T [], int) : 3
         imin(char *[], char *[], int) : 0
                   e               e      e         eee                e      e
confirme que les mod`les les plus sp´cialis´s sont pr´f´r´s aux moins sp´cialis´s.

5.2       e
       Mod`les de classes
            e                                e e
   Un mod`le de classe est un type param´tr´, par d’autres types et par des valeurs connues lors de la
compilation.
                  e                e             e                                              e e
   Par exemple, d´finissons un mod`le pour repr´senter des tableaux dans lesquels l’indice est v´rifi´ lors de
          e               ee                                                    e          e
chaque acc`s. Le type des ´l´ments de ces tables n’est pas connu, c’est un param`tre du mod`le :

         template<class TElement> class Table {
             TElement *tab;
             int nbr;
         public:
             Table(int n) {
                 tab = new TElement[nbr = n];
             }
             ~Table() {
                 delete [] tab;
             }
             TElement &operator[](int i) {
                      o                e
                 contrˆle de la validit´ de i
                 return tab[i];
             }
             TElement operator[](int i) const {
                      o                e
                 contrˆle de la validit´ de i
                 return tab[i];
             }
         };
                                      e                     e                                   `
   On obtient l’instanciation d’un mod`le de classe en en cr´ant des objets. Ici, contrairement a ce qui se passe
                                         e           e                     e
pour les fonctions, les arguments du mod`le doivent ˆtre toujours explicit´s :
          Table<char> message(80);
          Table<Point> ligne(100);

              e    e
    Une fois d´clar´s, ces tableaux s’utilisent comme les tableaux du langage (avec en plus la certitude que si
          e                    e
l’indice d´borde on en sera pr´venu tout de suite) :
          message[0] = ’ ’;
          ligne[i] = Point(j, k);
                                e                           e
   Tout se passe comme si la d´finition de l’objet message ´crite ci-dessus produisait le texte obtenu en
                                           e                 e
substituant TElement par char dans le mod`le Table et le pr´sentait au compilateur ; un peu plus tard, la
 e                                                                                   e      e
d´finition de ligne produit le texte obtenu en substituant TElement par Point et le pr´sente ´galement au
compilateur.




                                                       51
    C’est un traitement apparent´ au d´veloppement des macros18 mais bien plus complexe, aussi bien sur
                                  e      e
                                            e          e                                e
le plan formel (les arguments d’un mod`le peuvent ˆtre des instances d’autres mod`les, les modalit´s dee
                           e                       e                       e
l’instanciation d’un mod`le de fonction peuvent ˆtre automatiquement d´duites de l’appel de la fonction,
                                 e      e                            e            eaee             e       e
etc.) que sur le plan pratique (n´cessit´ de ne pas instancier un mod`le qui l’a d´j` ´t´ dans la mˆme unit´
                  e      e           ıtre                        e       e                       e
de compilation, n´cessit´ de reconnaˆ que des instances du mˆme mod`le produites et compil´es dans des
     e                  e e               e       e
unit´s de compilation s´par´es sont la mˆme entit´, etc.).

5.2.1                                e
           Fonctions membres d’un mod`le
                                      e                           e                          e           e
    Les fonctions membres d’un mod`le de classe sont des mod`les de fonctions avec les mˆmes param`tres que
        e                                             e                e       e       a      e            e
le mod`le de la classe. Cela est implicite pour les d´clarations et d´finitions ´crites ` l’int´rieur du mod`le de
                  e            e           e         e       a      e
classe, mais doit ˆtre explicit´ pour les d´finitions ´crites ` l’ext´rieur.
                                   e                                                 e e
    Par exemple, voici notre mod`le de classe Table avec des fonctions membres s´par´es :
         template<class TElement> class Table {
               TElement *tab;
               int nbr;
         public:
               explicit Table(int);
               ~Table();
               TElement &operator[](int);
               TElement operator[](int) const;
         };
         template<class TElement>
               Table<TElement>::Table(int n) {
                   tab = new TElement[nbr = n];
               }
         template<class TElement>
               Table<TElement>::~Table() {
                   delete [] tab;
               }
         template<class TElement>
               TElement &Table<TElement>::operator[](int i) {
                         o                e
                   contrˆle de la validit´ de i
                   return tab[i];
               }
         template<class TElement>
               TElement Table<TElement>::operator[](int i) const {
                         o                e
                   contrˆle de la validit´ de i
                   return tab[i];
               }
               e             e                                             e
   Les param`tres des mod`les de classes peuvent avoir des valeurs par d´faut. Voici, par exemple, une autre
                                                                                        e
version de nos tables, dans laquelle l’allocation du tableau n’est pas dynamique ; par d´faut, ce sont des tables
de 10 entiers :
           template<class TElement = int, int N = 10>
               class TableFixe {
                   TElement tab[N];
               public:
                   TElement & operator[](int i) {
                       IndiceValide(i, N);
                       return tab[i];
                   }
 18 Dans            e                           e    e        e                                                 e
           les premi`res versions de C++ les mod`les ´taient d´finis comme des macros et pris en charge par le pr´processeur.




                                                              52
                    TElement operator[](int i) const {
                        IndiceValide(i, N);
                        return tab[i];
                    }
               };
          TableFixe<float, 10> t1;                //   Oui, table de 10 float
          TableFixe<float> t2;                    //   Oui, table de 100 float
          TableFixe<> t3;                         //   Oui, table de 100 int
          TableFixe t4;                           //   ERREUR

                                e        e
    Comme une classe, un mod`le peut ˆtre annonc´ , c’est-`-dire, d´clar´ et non d´fini. Il pourra alors ˆtre
                                                      e      a        e    e          e                 e
      e                           e                     ıtre                e
utilis´ dans toute situation ne n´cessitant pas de connaˆ sa taille ou ses d´tails internes :

          template<class TElement> class Table;
          Table<float> table1;            // ERREUR
          Table<float> *ptr;              // Oui
          ptr = new Table<float>;         // ERREUR
          extern Table<float> table2;     // Oui

6     Exceptions
6.1    Principe et syntaxe
                           e
    Il y a parfois un probl`me de communication au sein des grands logiciels. En effet, le concepteur d’une
         e                                       e                                                    e
biblioth`que de bas niveau peut programmer la d´tection d’une situation anormale produite durant l’ex´cution
d’une de ses fonctions, mais ne sait pas quelle conduite doit adopter alors la fonction de haut niveau qui l’a
       e                                              ıt         e       e
appel´e. L’auteur des fonctions de haut niveau connaˆ la mani`re de r´agir aux anomalies, mais ne peut pas
      e
les d´tecter.
          e                               ea                                                  e
    Le m´canisme des exceptions est destin´ ` permettre aux fonctions profondes d’une biblioth`que de notifier
                                                                       e
la survenue d’une erreur aux fonctions hautes qui utilisent la biblioth`que.
                  e          e
    Les points-cl´s de ce m´canisme sont les suivants :
    – la fonction qui d´tecte un ´v´nement exceptionnel construit une exception et la lance (throw ) vers la
                        e          e e
                              e
      fonction qui l’a appel´e ;
                                             ıne                                        e
    – l’exception est un nombre, une chaˆ ou, mieux, un objet d’une classe sp´cialement d´finie dans cee
                                   e e
      but (souvent une classe d´riv´e de la classe exception) comportant diverses informations utiles a la       `
             e              e e          a
      caract´risation de l’´v´nement ` signaler ;
                    e                                               e
    – une fois lanc´e, l’exception traverse la fonction qui l’a lanc´e, sa fonction appelante, la fonction appelante
                                              a                                     a
      de la fonction appelante, etc., jusqu’` atteindre une fonction active (c.-`-d. une fonction commenc´e et  e
      non encore termin´e) qui a pr´vu d’ attraper (catch) ce type d’exception ;
                          e            e
                                                                    e
    – lors du lancement d’une exception, la fonction qui l’a lanc´e et les fonctions que l’exception traverse sont
           e                    e                                   ` e
      imm´diatement termin´es : les instructions qui restaient a ex´cuter dans chacune de ces fonctions sont
                 e           e             e    e      e                                            e
      abandonn´es ; malgr´ son caract`re pr´matur´, cette terminaison prend le temps de d´truire les objets
                                                     e
      locaux de chacune des fonctions ainsi avort´es ;
                                 `                                                                            e
    – si une exception arrive a traverser toutes les fonctions actives, car aucune de ces fonctions n’a pr´vu de
      l’attraper, alors elle produit la terminaison du programme.
                                      e                                                     e
    Une fonction indique qu’elle s’int´resse aux exceptions qui peuvent survenir durant l’ex´cution d’une cer-
       e                                                            e             e
taine s´quence d’instructions par une expression qui d’une part d´limite cette s´quence et qui d’autre part
                        e
associe un traitement sp´cifique aux divers types d’exception que la fonction intercepte. Cela prend la forme
d’un bloc try suivi d’un ensemble de gestionnaires d’interception ou gestionnaires catch :




                                                         53
            try {
                 instructions susceptibles de provoquer, soit directement soit dans des
                                e
                 fonctions appel´es, le lancement d’une exception
            }
                     e                e
            catch(d´clarationParam`tre 1 ) {
                                                                                        e
                 instructions pour traiter les exceptions correspondant au type de param`tre 1
            }
                     e                e
            catch(d´clarationParam`tre 2 ) {
                                                                     e
                 instructions pour traiter les exceptions, non attrap´es par le
                                e e                                       e
                 gestionnaire pr´c´dent, correspondant au type de param`tre 2
            }
            etc.
            catch(...) {
                 instructions pour traiter toutes les exceptions
                            e                           e e
                 non attrap´es par les gestionnaires pr´c´dents
            }

                     e        e
    Un bloc try doit ˆtre imm´diatement suivi par au moins un gestionnaire catch. Un gestionnaire catch
                   e             e                       e               e
doit se trouver imm´diatement apr`s un bloc try ou imm´diatement apr`s un autre gestionnaire catch.
                                              e            e                                                 e
    Chaque gestionnaire catch comporte un en-tˆte avec la d´claration d’un argument formel qui sera initialis´,
a         e                                                             e
` la mani`re d’un argument d’une fonction, avec l’exception ayant activ´ le gestionnaire. Exemple :
         catch(bad_alloc e) {
              cerr << "ERREUR : " << e.what() << ’\n’;
              }
                                   ee                                          e                   ` e
   Si le corps du gestionnaire ne r´f´rence pas l’exception en question, l’en-tˆte peut se limiter a sp´cifier le
type de l’exception :
            catch(bad_alloc) {
                cout << "ERREUR : Allocation impossible";
                }

   Un programme lance une exception par l’instruction
      throw expression;
                                         e e                                            e
   La valeur de expression est suppos´e d´crire l’exception produite. Elle est propag´e aux fonctions actives,
      a                                               e
jusqu’` trouver un gestionnaire catch dont le param`tre indique un type compatible avec celui de l’expression.
                           e                e                e                                     e
Les fonctions sont explor´es de la plus r´cemment appel´e vers la plus anciennement appel´e (c.-`-d. du   a
                                     e          `    e                                                         e
sommet vers la base de la pile d’ex´cution) ; a l’int´rieur d’une fonction, les gestionnaires catch sont explor´s
dans l’ordre o` ils sont ´crits. Le gestionnaire19
              u          e
      catch(...)
                                                            e
attrape toutes les exceptions. Il n’est donc pas toujours pr´sent et, quand il l’est, il est le dernier de son groupe.
   D`s l’entr´e dans un gestionnaire catch, l’exception est consid´r´e trait´e (il n’y a plus de balle en l’air ).
     e         e                                                       ee        e
                 e                                    o          ea
A la fin de l’ex´cution d’un gestionnaire, le contrˆle est pass´ ` l’instruction qui suit le dernier gestionnaire de
                                          ` e                                      e
son groupe. Les instructions restant a ex´cuter dans la fonction qui a lanc´ l’exception et dans les fonctions
             a                                                   e                          e
entre celle-l` et celle qui contient le gestionnaire qui a attrap´ l’exception sont abandonn´es. Il est important de
                                      e                                     e a
noter que les objets locaux attach´s aux blocs brusquement abandonn´s ` cause du lancement d’une exception
                     e
sont proprement d´truits.
                                                                e
    Exemple. A plusieurs reprises nous avons vu des classes repr´sentant des tableaux dans lesquels les indices
´taieznt contrˆles. Voici comment cela pourrait s’´crire20 :
e             o                                   e
 19 Notez  que, pour une fois, les trois points ... font partie de la syntaxe du langage.
 20 C’est                ıve.                                               ıne         e
          une version na¨ Lancer, comme ici, une exception de type chaˆ de caract`res est vite fait, mais lancer une exception
                     e e                                    e        e
objet d’une classe, d´riv´e de la classe exception, express´ment d´finie dans ce but, serait plus puissant et utile.




                                                             54
         void IndiceValide(int i, int n) {
             if (i < 0 || i >= n)
                 throw "indice hors bornes";
         }
         class Vecteur {
             double *tab;
             int nbr;
         public:
             explicit Vecteur(int n) {
                 tab = new double[nbr = n];
                 }
             double &operator[](int i) {
                 IndiceValide(i, nbr);
                 return tab[i];
                 }
             ...
         };
   Utilisation :
         try {
             cout << "n ? "; cin >> n;
             Vecteur t(n);
             ...
             cout << "i, t[i] ? "; cin >> i >> x;
             t[i] = x;
             cout << "t[" << i << "] = " << x << ’\n’;
             ...
         }
         catch (char *message) {     // origine : IndiceValide
                           e
             cout << "Probl`me: " << message << ’\n’;
         }
         catch (bad_alloc) {         // origine : new
                           e                      e
             cout << "Probl`me d’allocation de m´moire\n";
         }
         catch (...) {               // toutes les autres exceptions
                           e             e
             cout << "Probl`me indetermin´\n";
         }
                           e
         cout << " - Termin´\n";
     e
   Ex´cution :
         i, t[i] ? 5 0.125
         t[5] = 0.125
         ...
         i, t[i] ? 25 0.250
              e
         Probl`me: indice hors bornes
               e
         Termin´

6.2    Attraper une exception
                 e
    Lorsque l’ex´cution d’une instruction lance une exception, un gestionnaire catch ayant un argument com-
                                     e                                    e                       e
patible avec l’exception est recherch´ dans les fonctions actives (commenc´es et non encore termin´es), de la
      e                e
plus r´cemment appel´e vers la plus anciennement appel´e. e
    Un argument d’un gestionnaire catch de type T1 , const T1 , T1 & ou const T1 & est compatible avec une
exception de type T2 si et seulement si :
    – T1 et T2 sont le mˆme type, ou
                         e


                                                     55
   – T1 est une classe de base accessible de T2 , ou
   – T1 et T2 sont de la forme B* et D* respectivement, et B est une classe de base accessible de D.
                                                                                                    e
   Notez que les conversions standard sur des types qui ne sont pas des pointeurs ne sont pas effectu´es. Ainsi,
par exemple, un gestionnaire catch(float) n’attrape pas des exceptions int.
                            o                                                                  e
    Note. Lorsque le contrˆle entre dans un gestionnaire catch, son argument est initialis´ (par l’exception
                   e              e                    `
effectivement lanc´e) selon un m´canisme analogue a celui de l’initialisation des arguments d’un appel de
fonction.
                    e                 e
    C’est donc la mˆme sorte de consid´rations que celles que l’on fait pour un appel de fonction qui permettent
                                        ee
de choisir le mode (valeur, adresse ou r´f´rence) de l’argument du gestionnaire. On notera en particulier que
    e                               ee
la d´claration comme pointeur ou r´f´rence est indispensable si on souhaite faire jouer le polymorphisme. Par
exemple, le programme suivant est maladroit :
         catch(exception e) {                            // maladroit
                               e
               cout << "Probl`me: " << e.what();
         }
                                                                                             e e
car on y appelle la fonction what de la classe exception, et l’affichage obtenu sera trop g´n´ral, dans le
                                                                                         e
genre : Probl`me : Unknown exception . En principe, l’exception effectivement lanc´e appartient a une
                e                                                                                     `
        e e                                                             ee     e
classe d´riv´e de exception, dans laquelle la fonction virtuelle what a ´t´ red´finie pour donner un message
   e                                                      e
int´ressant. Pour obtenir l’affichage de ce message il faut ´crire :
         catch(exception &e) {
             cout << "Exception: " << e.what();
         }
ou bien (mais dans ce cas il faut que l’instruction throw lance l’exception par adresse) :
       catch(exception *e) {
           cout << "Exception: " << e->what();
       }
                                   e                                      e
   Relance d’une exception. A l’int´rieur d’un gestionnaire catch on peut ´crire l’instruction
      throw;
                                  e                     e                                        e          e
elle indique que l’exception, qui ´tait tenue pour trait´e en entrant dans le gestionnaire, doit ˆtre relanc´e
                                 ee         e
comme si elle n’avait pas encore ´t´ attrap´e.

6.3     e                                                e
       D´claration des exceptions qu’une fonction laisse ´chapper
                                    e a      e                                                       e
    Lorsqu’une exception est lanc´e ` l’int´rieur d’une fonction dans laquelle elle n’est pas attrap´e on dit que
                     e
la fonction laisse ´chapper l’exception en question.
                                                                          e                 e            e
    Une fonction peut indiquer les types des exceptions qu’elle laisse ´chapper. Cela d´finit avec pr´cision, a   `
                                                              ın´
l’intention des utilisateurs de la fonction, les risques entraˆ es par un appel de cette fonction. La syntaxe est :
      en-tˆte de la fonction throw(type 1 , type 2 , ... type k )
          e
   Exemple :
         void IndiceValide(int i, int n) throw(char *) {
             if (i < 0 || i >= n)
                  throw "indice hors bornes";
         }
         class Vecteur {
             ...
             double &operator[](int i) throw(char *) {
                  IndiceValide(i, nbr);
                  return tab[i];
             }
             ....
         };


                                                           56
         e e                                    e        e
   Plus g´n´ralement, d’une fonction dont l’en-tˆte se pr´sente ainsi
      en-tˆte de la fonction throw(T1 , T2 *)
          e
ne peuvent provenir que des exceptions dont le type est T1 ou un type ayant T1 pour classe de base accessible
ou T2 * ou un type pointeur vers une classe ayant T2 pour classe de base accessible. L’expression
      en-tˆte de la fonction throw()
          e
indique qu’un appel d’une telle fonction ne peut lancer aucune exception.
                       e                               e                                        e
   En outre, on consid`re qu’une fonction dont l’en-tˆte ne comporte pas de clause throw laisse ´chapper
toutes les exceptions.
               e                                                                            e          e
    De telles d´clarations ne font pas partie de la signature : deux fonctions dont les en-tˆtes ne diff´rent qu’en
                           e                             e
cela ne sont pas assez diff´rentes pour faire jouer le m´canisme de la surcharge.
                                           e                                                  e            e
    Pour les exceptions explicitement lanc´es par la fonction, cette indication permet une v´rification d`s la
                     e          e
compilation, avec l’´mission d’´ventuels messages de mise en garde.
                                                                                                   e
    Qu’il y ait eu une mise en garde durant la compilation ou pas, lorsqu’une exception non attrap´e se produit
                                        e                       e e                              e        e
dans une fonction qui ne la laisse pas ´chapper, la fonction pr´d´finie unexpected() est appel´e. Par d´faut
                                e    `
la fonction unexpected() se r´duit a l’appel de la fonction terminate().

6.4    La classe exception
                                                           e e                               e    `
    Une exception est une valeur quelconque, d’un type pr´d´fini ou bien d’un type classe d´fini a cet effet.
                                 e                                   e
Cependant, les exceptions lanc´es par les fonctions de la biblioth`que standard sont toutes des objets de
         e e                   e       e          a
classes d´riv´es d’une classe d´finie sp´cialement ` cet effet, la classe exception, qui comporte au minimum
les membres suivants :

         class exception {
         public:
             exception() throw();
             exception(const exception &e) throw();
             exception &operator=(const exception &e) throw();
             virtual ~exception() throw();
             virtual const char *what() const throw();
         };
                                                                               e
    Les quatre premiers membres correspondent aux besoins internes de l’impl´mentation des exceptions. La
                             ıne          e
fonction what renvoie une chaˆ de caract`res qui est une description informelle de l’exception.
                                       e                                                    e        e
    On notera que, comme le montre la d´claration de cette classe, aucune exception ne peut ˆtre lanc´e durant
    e
l’ex´cution d’un membre de la classe exception.
                                                  e                                 e
    Bien que la question concerne plus la biblioth`que standard que le langage lui-mˆme, voici les principales
         e e
classes d´riv´es directement ou indirectement de exception :
      exception
                                                                  e
           bad exception : cette exception, en rapport avec la d´finition par l’utilisateur de la
                                              e
               fonction unexpected, signale l’´mission par une fonction d’une exception qui n’est
                     e   e
               pas d´clar´e dans sa clause throw.
                                                  e
           bad cast : cette exception signale l’ex´cution d’une expression dynamic cast invalide.
                                     e
           bad typeid : indique la pr´sence d’un pointeur p nul dans une expression typeid(*p)
           logic error : ces exceptions signalent des erreurs provenant de la structure logique in-
                                         e                        e        e
               terne du programme (en th´orie, on aurait pu les pr´voir en ´tudiant le programme) :
                   domain error : erreur de domaine,
                   invalid argument : argument invalide,
                                                 e                               e      a
                   length error : tentative de cr´ation d’un objet de taille sup´rieure ` la
                                           e                  e     e
                     taille maximum autoris´e (dont le sens pr´cis d´pend du contexte),


                                                       57
                   out of range : arguments en dehors des bornes.
                                                  ` e                            e
           bad alloc : cette exception correspond a l’´chec d’une allocation de m´moire.
           runtime error : exceptions signalant des erreurs, autres que des erreurs d’allocation de
                e                      e     e    e                  e
               m´moire, qui ne peuvent ˆtre d´tect´es que durant l’ex´cution du programme :
                   range error : erreur de rang,
                                     e                e
                   overflow error : d´bordement arithm´tique (par le haut),
                                      e                e
                   underflow error : d´bordement arithm´tique (par le bas).
                               e e                                        e    e
   Remarque. Les classes pr´c´dentes sont surtout des emplacements r´serv´s pour des exceptions que les
                        e                                          e
fonctions de la biblioth`que standard (actuelle ou future) peuvent ´ventuellement lancer ; rien ne dit que c’est
actuellement le cas.


 ee
R´f´rences
  B. Stroustrup
                    `  ´
  Le langage C++ (3eme edition)
  CampusPress, 1999
  H. Garreta
                           `
  Le langage et la bibliotheque C++ Norme ISO
  Ellipses, 2000
  J. Charbonnel
                                                          ´
  Langage C++, la proposition de standard ANSI/ISO expliquee
  Masson, 1996
       e
  A. G´ron, F. Tawbi
                  ´
  Pour mieux developper avec C++
  InterEditions, 1999
                                                    ♠♠♠




                                                      58

				
DOCUMENT INFO
Shared By:
Stats:
views:1
posted:3/28/2013
language:
pages:58
Description: C Learning