1242.2 Langage C++ - 2024-2025
1. Introduction
2. Modèles de fonction
3. Modèles de classe
Développer une classe de listes chainées d'entiers (
Développer une classe de listes chainées de
Développer une classe de listes chainées de pointeurs de
...
Développer une seule classe
CList<int> lint;
CList<Figure> lfig;
CList<Figure*> lpfig;
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é)
template <typename T>
class CList
{
private:
T *tab; // Generic member
int size;
public:
CList(int size) { ... }
void add(T e) { ... }
T get(int i) { ... }
};
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é)
CList<int> lstInt(3); // Instantiation with int
CList<Figure> lstFigure(5); // Instantiation with Figure
CList<Figure*> lstPtrFigure(7); // Instantiation with Figure*
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)
template<typename T1, typename T2, ... , typename Tn>
// AnyType is T1, T2, ..., Tn
AnyType nomfonct(AnyType arg1, ... , AnyType argN)
{
...
}
nomfonct<type1, ..., typen>(arg1, ..., argN);
template<typename T>
T maxi(const T &a, const T &b)
{
return (a >= b) ? a : b;
}
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);
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
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
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>"
}
Paramètres d'un modèle de fonction dont le type est imposé
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;
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)
}
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
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.
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();
}
...
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();
}
// 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> { };
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];
};
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;
}
#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;
}