Cours C++ 4
Théorie
Contenu:
1. Méthodes virtuelles vs switch
2. Classe abstraite et concrète
3. Polymorphisme
4. Destructeur  virtuel
5. Fonctionnement interne du virtuel...
6. Classe template
7. Paramètre de template qui n'est pas un type
8. Typedef et enum dans des classes
9. STL: Standard Template  Library
10. Itérateurs
11. Algorithmes
12. Classe vector
13. Classe list
14. Classe deque
15. Classes set & multiset
16. Classes map & multimap


0. Retour sur la séance pratique.

1. Méthodes virtuelles vs switch

- Les méthodes virtuelles permettent d'écrire des algorithmes généraux sans avoir à tout remodifier si jamais une nouvelle classe était ajoutée et devait être traitée par cet algorithme.
- Pour les éléments finis, il existe un algorithme spécifique à chaque type d'élément qui lui permet de calculer son barycentre.  Alors, lorsqu'on demande à un élément de calculer son barycentre, on voudrait que le programme choisisse dynamiquement (lors de l'exécution) le bon code à exécuter.
- Si nous déclarons la méthode calculBarycentre virtuelle dans la classe de base (voir ex.) et que nous utilisons un pointeur ou une référence à un objet d'une classe dérivée et que nous appelons la méthode sur le pointeur (ex. : lElementPtr->calculBarycentre()), alors le programme va choisir le bon code à exécuter (celui défini dans la classe dérivée).

Exemple :
class Element
{
public:
    ...
    virtual Vecteur3D calculBarycentre() const;
    ...
};

class Element1D : public Element
{
public:
    ...
    virtual Vecteur3D calculBarycentre() const;
    ...
private:
    Vecteur3D aNoeud0, aNoeud1;
};
 

- Si on veut faire un switch sur le type de classe, on devrait modifier BEAUCOUP de code en cas d'ajout d'une nouvelle classe.

2. Classe abstraite et concrète

- Une classe est normalement pensée pour instancier des objets.
- Il existe des types de classes qui ne voudraient rien dire, mais représenteraient quand même un certain concept (par exemple, notre classe Elément ne représente pas un type d'élément concret, mais bien les notions qui sont communes à tous les types d'éléments).
- On ne peut instancier des objets de ce type, seulement des pointeurs et références.
- Une classe devient abstraite, dès que l'on ajoute un =0 à la fin du prototype d'une méthode (cette méthode est alors qualifiée "méthode virtuelle pure").
- Une méthode virtuelle pure doit obligatoirement être redéfinie dans les classes dérivées.

Exemple :
class Element {
public:
...
virtual Vecteur3D calculBarycentre() const = 0;
...
};

3. Polymorphisme

- Possibilité pour des objets de différentes classes reliés à une même classe de base de répondre différemment à un message.
- Le polymorphisme permet l'extensibilité du code existant.  En effet, on peut ajouter facilement une nouvelle classe héritière à une classe de base et on s'assure ensuite de répondre correctement à chacune des méthodes virtuelles qui sont définies dans le parent.
- On peut avoir un vecteur de pointeur vers des éléments et leur demander calculeBarycentre(), ou toute méthode (virtuelle) définie sur la classe de base.

4. Destructeur virtuel

- Nécessaire avec l'allocation dynamique.
- Un fait à remarquer : normalement, une méthode virtuelle doit porter le même nom dans le parent et les enfants pour que l'appel de la méthode sur le parent "descende" jusqu'aux enfants.  Le destructeur est une exception, puisqu'il change de nom selon la classe.  Les destructeurs ne portent donc pas le même nom, mais sont une même méthode virtuelle quand même.
- On comprend toute l'importance du destructeur virtuel lorsqu'on appelle l'opérateur delete sur une classe de base.

5. Fonctionnement interne du virtuel...

- Chaque classe a une vtable qui est un vecteur qui contient les adresses de toutes les méthodes virtuelles.
- Chaque objet a son pointeur à la bonne vtable de sa classe.
- À l'exécution, on a la fonction qui détermine le offset (l'indice du vecteur) dans la vtable, pour la méthode appelée.

6. Classe template

- On a utilisé les templates avec la fonction min et norme, afin de réutiliser le même algorithme, peu importe le type passé.
- Il existe aussi des classes templates (patrons de classe en bon français).
- Une liste chaînée et un vecteur sont des exemples où, peu importe le type, le comportement du conteneur demeure inchangé.
- On ajoute les mots clefs template <class PTType> devant la classe.

Exemple :

template <class PTType>
class VectDyn {
public:
    VectDyn();
    VectDyn(int pDim);
    ...
    PTType& operator[](int pIndice);
    ...
private:
    PTType * aVect;
    int aDim;
};

int main() {

    VectDyn<double> lVectDouble;
    VectDyn<Heure> lVectHeure;
    VectDyn<Element*> lVectElement;
}

- La définition des méthodes doit comporter quelques nouveaux éléments:
Exemple :
template <class PTType>
VectDyn<PTTYpe>::VectDyn()
: aVect(0), aDim(0)
{

}
template <class PTType>
VectDyn<PTTYpe>::VectDyn(int pDim)
{
    assert(pDim > 0 && "Dimension inférieure ou égale à 0");
    aDim = pDim;
    aVecteur = new PTType[aDim];
}

7. Paramètre de template qui n'est pas un type

- On peut passer autre chose que des types pour des templates.
- On peut aussi passer des valeurs comme des entiers.

Exemple :

template <class PTType, int PTDim>
class VectFixe {
public:
    VectFixe();
    ...
private:
    PTType aVect[PTDim];
};

int main() {

    VectFixe<double, 4> lVectDouble;
    VectFixe<Heure, 10> lVectHeure;
    VectFixe<Element*, 20> lVectElement;
}

8. Typedef et enum dans des classes

- Lorsque l'on fait un typedef dans une fonction ou dans le <<scope>> global, on peut utiliser directement le nom de type que l'on vient de définir.  C'est la même chose pour les énumérations.

Exemple :

typedef int Entier;
enum Couleur {ROUGE, BLANC};

int main()
{
    Entier lNo = 10;
    Couleur lCoul = ROUGE;
}

- Dans une classe, on peut aussi faire des typedef et déclarer des énumérations.  Toutefois, pour utiliser ces dernières, il faudra d'abord faire précéder leur nom du <<scope>> de la classe.

Exemple :

class Maillage
{
public:
    typedef   Arete TypeArete;
    typedef   Sommet TypeSommet;
    typedef   Sommet* IterateurSommet;
    ...
};

class ParcoursElementProcheEnProche
{
public:
    enum TypeVoisin {AvecAutreDimAvecMultiple = 111,
                     SansAutreDimAvecMultiple,
                     SansAutreDimSansMultiple};
...
};
 

int main()
{
    Maillage::TypeArete lArete;

    Maillage::IterateurSommet lIterSommet = lArete.reqSommet(0);
    lIterSommet->reqCoor();
    ...
    ParcoursElementProcheEnProche::TypeVoisin lTypeVoisin;
    lTypeVoisin = ParcoursElementProcheEnProche::SansAutreDimAvecMultiple;
    ...
}

9. STL: Standard Template Library

- STL est divisé en 3 parties : conteneurs, itérateurs et algorithmes.
- Toutes les classes et fonctions de STL sont définies dans le namespace std.

Conteneurs:

- On peut voir un conteneur comme un livre que plusieurs peuvent lire en même temps et dans lequel on a placé plusieurs signets (les itérateurs).

   Séquentiels:
    - vector
    - deque
    - list

   Associatifs:
    - set
    - multiset
    - map
    - multimap

   Adaptateur de conteneur:
    - stack
    - queue
    - priority_queue

Méthodes:
  - Constructeur, CC & destructeur
  - bool empty()     // true si le conteneur est vide
  - size()           // retourne la dimension
  - operator =       // Opérateur d'assignation
  - swap             // Interchange 2 conteneurs du même type
  - begin(), end()   // Retourne le début/fin du conteneur
  - rbegin(), rend() // Retourne la fin/début du conteneur, permet de parcourir le conteneur à l'envers
  - erase            // Enlève une ou des entrées du conteneur
  - void clear()     // Vide le conteneur

Typedef dans les conteneurs:

  - value_type
  - reference & const_reference
  - pointer & const_pointer
  - iterator & const_iterator
  - reverse_iterator & const_reverse_iterator
  - difference_type    // (résultat de la soustraction de 2 itérateurs)
  - size_type          //(le type utilisé pour compter les entrées)

10. Itérateurs

- On a vu comment parcourir un vecteur avec des pointeurs.
- Les itérateurs servent à parcourir (se déplacer dans) les conteneurs.
- Les conteneurs STL offrent les méthodes begin() et end() qui retournent des itérateurs vers le début et la fin du conteneur.

Exemple :

int main() {
    vector<double> lVect(10);
    vector<double>::iterator lIterVect, lIterFin;

    lIterVect = lVect.begin();
    lIterFin = lVect.end();

    while (lIterVect != lIterFin) {
        cout << *lIterVect << endl;
        ++lIterVect;
    }
}

11. Algorithmes

- Les algorithmes utilisent les itérateurs et non les conteneurs.
- Ils sont définis à l'extérieur des conteneurs, favorisant la réutilisation du même algorithme, peu importe le conteneur.
- Il y en a près de 70 fournis par STL...

12. Classe vector

- Le vector ressemble au vecteur C (les array).
- méthodes:
    size()       // Retourne la dimension du vecteur
    capacity()   // Retourne la capacité maximale actuelle du vecteur
    reserve()    // Réserve de la mémoire: augmente la capacité, sans changer la dimension
    resize()     // Augmente la capacité et la dimension
    push_back()  // Ajoute une entrée à la fin du vecteur, augmentant ainsi sa dimension et la capacité, si nécessaire
    pop_back();  // Enlève le dernier élément, diminuant la dimension
    operator[];  // Accède à l'entrée de l'indice passé

13. Classe list

- Représente un liste chaînée. Très pratique pour ajouter et effacer partout
- méthodes:
    - push_front() // Ajoute une entrée au début de la liste
    - pop_front()  // Enlève une entrée au début de la liste
    - sort()       // trie la liste
    - splice()     // Enlève les entrées de la 2e liste et les déplace dans la liste.
    - merge()      // Avec les 2 listes triées, prend toutes les entrées de la 2e liste et les insère au bon endroit dans la liste. Le résultat est une liste triée et la 2e liste vide.
    - unique()     // Dans une liste triée, enlève toutes les entrées doubles.
    - swap()       // Interchange le contenu de 2 listes.
    - reverse()    // Inverse l'ordre des entrées du conteneur.
    - remove()     // Enlève les entrées de valeur X.

14. Classe deque

- S'utilise comme un vecteur avec des crochets pour accéder aux composantes ([])
- Optimisé pour l'ajout/l'effacement à la fin et au début du conteneur.

15. Classes set & multiset

- Le set est un conteneur qui contient des entrées triées et uniques.
- Le multiset est comme le set, mais il peut contenir plusieurs entrées égales.

16. Classes map & multimap

- Le map est un conteneur associatif qui associe une clef à une définition (ou une clef à une valeur).
- Les entrées sont triées et uniques.
- Le multimap est comme un map, sauf qu'il peut contenir plusieurs clefs égales.