Logo HE-ARC Logo HES-SO

1242.2 Langage C++ - 2024-2025

Chapitre 7

Les modèles

1. Introduction

2. Modèles de fonction

3. Modèles de classe

7.1 Introduction

Problème

Développer une classe de listes chainées d'entiers (CListInt

Développer une classe de listes chainées de Figure (CListFigure)

Développer une classe de listes chainées de pointeurs de Figure (CListPtrFigure)

...

Solution

Développer une seule classe CList pour un type générique et l'instancier lors de son utilisation


            CList<int> lint;
            CList<Figure> lfig;
            CList<Figure*> lpfig;
          

7.1 Introduction

Un modèle est un code qui est indépendant du type de données

Il prend un ou plusieurs types en paramètres

⇒ Code générique (généricité)


Définition


            template <typename T>
            class CList
            {
              private:
                T *tab; // Generic member
                int size;
              public:
                CList(int size) { ... }
                void add(T e) { ... }
                T get(int i) { ... }
            };
            

7.1 Introduction

Un modèle est un code qui est indépendant du type de données

Il prend un ou plusieurs types en paramètres

⇒ Code générique (généricité)


Utilisation (instanciations)


            CList<int> lstInt(3); // Instantiation with int
            CList<Figure> lstFigure(5); // Instantiation with Figure
            CList<Figure*> lstPtrFigure(7); // Instantiation with Figure*
          

7.1 Introduction

On peut créer des modèles pour :

- des fonctions (évite de surcharger une fonction identique)

- des classes (évite de créer plusieurs classes identiques)


Synonymes

Class template : modèle de classe, patron de classe, classe générique

Function template : modèle de fonction, patron de fonction, fonction générique

7.2 Modèles de fonction

Définition


            template<typename T1, typename T2, ... , typename Tn>
            // AnyType is T1, T2, ..., Tn
            AnyType nomfonct(AnyType arg1, ... , AnyType argN)
            {
              ...
            }
          

Utilisation


            nomfonct<type1, ..., typen>(arg1, ..., argN);
          

7.2 Modèles de fonction

Exemple


            template<typename T>
            T maxi(const T &a, const T &b)
            {
              return (a >= b) ? a : b;
            }
            

Utilisation


            int i1, i2; 
            float f1, f2; 
            double d1,d2;
            
            maxDouble = maxi<double>(i1, d2); // Explicit
            maxInt = maxi<int>(f1, d2); // Explicit
            maxInt = maxi(i1, i2); // Implicit
            maxFloat = maxi(f1, f2); // Implicit
            maxDouble = maxi(d1, d2); // Implicit
            maxDouble = maxi(i1, d2); // ⛔ error

            Time t1(10,57,59), t2(9,58,37);
            Time maxTime = maxi<Time>(t1, t2);
            maxTime = maxi(t1, t2);
            

7.2 Modèles de fonction

Commentaires

Le type de retour peut être un type générique

Les opérateurs utilisés dans le modèle de fonction doivent être définis par les classes (types) utilisées

Le modèle de fonction est instancié par le compilateur, en fonction des appels

Le compilateur cherche une correspondance absolue des types : il ne réalise pas de conversions implicites

7.2 Modèles de fonction

Commentaires

Chaque type générique doit être dans l'entête du modèle

Les modèles de fonctions sont dans le fichier d'interface (.h) - Aussi pour les méthodes de classes !

Il est possible de surcharger et même de spécialiser des modèles de fonctions. La spécialisation est prioritaire (voir page suivante).

On peut contraindre l'instanciation des modèles à certains type avec les Concepts (C++20)

7.2 Spécialisation


            template<typename T>
            void f(int)
            {
              cout << "Non template" << endl;
            }            
            template<typename T>
            void f(T)
            {
              cout << "Template" << endl;
            }                        
            template<> // Total specialization
            void f<int>(int)
            {
              cout << "Spécialisation f<int>" << endl;
            }            
            int main()
            {
              f(1); // Prints "Non template"
              f('c'); // Prints "Template"
              f<>(1); // Prints "Spécialisation f<int>"
            }
            
⚠️ ATTENTION : le code de la spécialisation doit être dans le fichier d'implémentation (.cpp) et pas dans le fichier d'interface (.h) !

7.2 Paramètres expressions

Paramètres d'un modèle de fonction dont le type est imposé

Syntaxe

            
            template<typename T, typename T2, int N, double K>
          

Les valeurs par défaut s'appliquent aux paramètres de type et aux paramètres expressions

            
            template<typename T = int, int N = 10>
            TableFixe<> tablo;
          

7.2 Paramètres expressions

Exemple


            template<typename T> T mini(T a, T b) // Template 1
            {
              return (a <  b) ? a : b;
            }
            
            template<typename T> T mini(T *t, int n) // Template 2
            {
              T min = t[0];
              for (int i = 1; i < n; i++)
              if (t[i] < min) min=t[i];
              
              return min;
            }
            
            int main()
            {
              long n=2, p=12; float t[4] = {1.23, 66.65, -12.34, 100};
              x=mini(n,p); // Calls long mini(long, long)
              x=mini(t,4); // Calls float mini(float*, int)
            }
          

7.2 Choix de la fonction appelée

Le compilateur choisit la fonction à appeler en fonction des types des arguments

Priorités pour le compilateur

1. fonction ordinaire

2. instances de modèles plus spécialisées

3. instances de modèles moins spécialisées

7.3 Modèles de classe

Comme pour les fonctions, on peut créer des classes génériques

Très utile pour créer des conteneurs de données comme par exemple les conteneurs de la Standard Template Library (chap. 9)

Définition de méthodes : préfixer le nom de la classe par l'entête du modèle.

Pour instancier un modèle de classe, les paramètres expressions doivent être des constantes.

7.3 Modèles de classe - Exemple


                template<typename T>
                class Point
                {
                  T x; T y; // Generic members

                  public:
                    Point(T abs=0, T ord=0)
                    {
                      x=abs;
                      y=ord;
                    }
                    void show();
                };
                
                template<typename T>
                void Point<T>::show()
                {
                  cout << x << " " << y << endl;
                }
              

                #include <iostream>
                #include "Point.h"
                
                using namespace std;
                
                int main()
                {                  
                  Point<int> ptInt(3,5);
                  // "3 5"
                  ptInt.show();
                  
                  Point<char> ptChar('d','y');
                  // "d y"
                  ptChar.show();

                  Point<double> ptDbl(3.5,2.3);
                  // "3.5 2.3"
                  ptDbl.show();
                }
              

7.3 Modèles de classe - Exemple


                  ...
    
                  template<>
                  void Point<char>::show()
                  {
                    cout << (int)x << " "
                         << (int)y << endl;
                  }            
    
                  ...
                

                  #include <iostream>
                  #include "Point.h"
                  
                  using namespace std;
                  
                  int main()
                  {                  
                    Point<int> ptInt(3,5);
                    // "3 5"
                    ptInt.show();
                    
                    Point<char> ptChar('d','y');
                    // "100 121"
                    ptChar.show();
  
                    Point<double> ptDbl(3.5,2.3);
                    // "3.5 2.3"
                    ptDbl.show();
                  }
                

7.3 Spécialisation partielle


                // primary template
                template<typename T1, typename T2, int I>
                class A{ };
                
                // #1 partial specialization - T1 is a pointer
                template<typename T1, typename T2, int I> 
                class A<T1*, T2, I> { }; 

                // #2 partial specialization - T2 is a pointer
                template<typename T1, typename T2, int I> 
                class A<T1, T2*, I> { }; 

                // #3 partial specialization - T2 is a pointer to T1
                template<typename T, int I>
                class A<T, T*, I> { }; 

                // #4 partial specialization - T1 is an int, T2 is a pointer, I is 5
                template<typename T> 
                class A<int, T*, 5> { };
                

⚠️ ATTENTION : la spécialisation partielle est valable pour les modèles de classe et pas pour les modèles de fonction !

7.3 Exemple


                template<typename T, int taille>
                class CustomArray
                {
                  public:
                    CustomArray() {};
                    T getAt(int pos) const ;
                    void setAt(int pos, T val);
                    int getSize() const {return taille;}
                
                  private:
                    T tab[taille];
                };
              

7.3 Exemple


                template<typename T, int taille>
                T CustomArray<T,taille>::getAt(int pos) const
                {
                  if (pos >= 0 && pos < taille)
                    return tab[pos];
                  else return -1;
                }
                
                template<typename T, int taille>
                void CustomArray<T,taille>::setAt(int pos, T val)
                {
                  if (pos >= 0 && pos < taille) tab[pos]=val;
                }
              

7.3 Exemple


                #include "CustomArray.h"
                int main()
                {
                  CustomArray<double, 5> t1;
                  typedef CustomArray<int, 7> tabIntTy;
                  tabIntTy t2;
                  
                  for (int i =- 1; i <= t1.getSize(); i++)
                  {
                    t1.setAt(i, i/2.);
                    cout << t1.getAt(i) << "\t";
                  }
                  
                  for (int i =- 1; i <= t2.getSize(); i++)
                  {
                    t2.setAt(i, i/2);
                    cout << t2.getAt(i) << "\t";
                  }
                  
                  return 0;
                }