0 évaluation0% ont trouvé ce document utile (0 vote)
9 vues23 pages
CH19-POO-C
Ce document présente une introduction aux templates en C++, permettant de créer des fonctions et classes génériques qui acceptent différents types de données. Il explique comment déclarer des types génériques, utiliser des fonctions templates pour éviter la répétition de code, et les contraintes liées à l'utilisation de ces templates. Enfin, il aborde la spécialisation des templates et la création de classes templates pour modéliser des objets avec des types variables.
Téléchargez aux formats PDF ou lisez en ligne sur Scribd
0 évaluation0% ont trouvé ce document utile (0 vote)
9 vues23 pages
CH19-POO-C
Ce document présente une introduction aux templates en C++, permettant de créer des fonctions et classes génériques qui acceptent différents types de données. Il explique comment déclarer des types génériques, utiliser des fonctions templates pour éviter la répétition de code, et les contraintes liées à l'utilisation de ces templates. Enfin, il aborde la spécialisation des templates et la création de classes templates pour modéliser des objets avec des types variables.
Téléchargez aux formats PDF ou lisez en ligne sur Scribd
Créez des templates
o <@
eon
La force des templates est d'autoriser une fonction ou une
classe a utiliser des types différents. Leur marque de
fabrique est la présence des chevrons < et >
@
Lutilisation des templates est un sujet trés vaste. II
y a des livres entiers qui y sont consacrés. Ce
chapitre n'est qu'une bréve introduction au
domaine.
Découvrez les fonctions
template
ll arrive souvent qu'on ait besoin d'opérations
mathématiques dans un programme. Une opération toute
simple est celle qui consiste a trouver le plus grand de
deux nombres. Dans le cas des nombres entiers, on
pourrait écrire une fonction comme suit
cpp
maximum(
(a>b)Une telle fonction existe dans la bibliotheque
standard. Elle se trouve dans I'en-téte
algorithm ,ets'appelle maximum
Cette fonction est trés bien et elle n'a pas de
probléme. Cependant, si un utilisateur de votre
fonction souhaite utiliser des double ala place des
int _, il risque d'avoir un probleme. II faudrait donc
fournir également une version de cette fonction
utilisant des nombres réels.
Pour 6tre rigoureux, il faudrait également fournir une
fonction de ce type pourles char ,les
unsigned int, les nombres rationnels, etc. On se
rend vite compte que la tache est trés répétitive.
Cependant, il y a un point commun a toutes ces
fonctions : le corps de la fonction est strictement
identique. Quel que soit le type, le traitement que l'on
effectue est le méme. On se rend compte que
l'algorithme utilisé dans la fonction est générique.
ll serait donc interessant de pouvoir écrire une seule
fois la fonction en disant au compilateur :
"Cette fonction est laméme pour tous les types,
fais le sale boulot de recopie du code toi-méme
Eh bien, cela tombe bien parce que c'est ce que
permettent les templates en C++, et c'est ce que
nous allons apprendre a utiliser...Le terme francais pour template est modéle.
Le nom est bien choisi car il décrit précisément
ce que nous allons faire. Nous allons écrire un
modeéle de fonction, et le compilateur va utiliser
ce modeéle dans les différents cas qui nous
intéressent.
Déclarez un type générique
Pour indiquer au compilateur que l'on veut faire une
fonction générique, on déclare un "type variable" qui
peut représenter n'importe quel autre type. On parle
de type générique
Cela se fait de la maniére suivante
cpp
Vous pouvez remarquer quatre choses importantes :
1. Le mot-clé template prévient le compilateur
que la prochaine chose dont on va lui parler sera
générique.
[Link] < et > constituent la marque
de fabrique des templates.
3. Le mot-clé typename indique au compilateur
que T serale nom que l'on va utiliser pour
notre "type spécial" qui remplace n'importe quoi.
4. Il n'y a PAS de point-virgule a la fin de la ligne.La ligne de code précédente indique au compilateur
que dans la suite, T sera un type générique pouvant
représenter n'importe quel autre type. On pourra donc
utiliser ce T dans notre fonction comme type pour
les arguments et pour le type de retour.
cpp
Peso UES
T maximum aa ay
i
if(a>b)
rte
se
ety
Quand il voit cela, le compilateur génére
automatiquement une série de fonctions maximum
pour tous les types dont vous avez besoin. Cela veut
dire que si vous avez besoin de cette fonction pour
des entiers, le compilateur crée la fonction :
eesti Ga Te ae oS
if(a>b)
return a;
os
ot
..etdeméme pourles double , char ,etc
C'est le compilateur qui se farcit le travail de recopie !
Parfait, on peut aller faire la sieste pendant ce temps.vere
T maximum(
{
(a>b)
int main()
f
double pi( Oe
(ol AG
maximum(pi,e) << endl;
int cave(-1);
rae ar 310
cout << maximum(cave,dernierEtage)
Pere Tae
Pra Ura YG
maximum(a,b)Et tout cela se passe sans que |'on ait besoin d'écrire
plus de code. II faut juste indiquer entre des chevrons
quelle version de la fonction on souhaite utiliser,
comme pourles vector ensomme: on devait
indiquer quelle version du tableau on souhaitait utiliser.
Il n'est pas toujours utile d'indiquer entre
chevrons quel type on souhaite utiliser pour les
fonctions templates. Le compilateur est assez
intelligent pour deviner ce que vous souhaitez
faire. Mais dans des cas compliqués ou s'il y a
plusieurs arguments de types différents, alors il
devient néecessaire de spécifier la version.
int main()
> pi(3
e(2
fetolth a maximum(pi,e) endl;
Le compilateur voit dans ce cas que l'on souhaite
utiliser la version double de la fonction.
A vous de voir si votre compilateur comprend vos
intentions.Si vous 6tes attentif, vous avez peut-étre remarqué
que, pour les arguments, j'ai remplacé le passage par
valeur par des références constantes. En effet, on ne
sait pas quel type l'utilisateur va utiliser avec notre
fonction maximum. La taille en mémoire de ce type
sera peut-étre trés grande : on passe donc une
référence constante pour éviter une copie cofiteuse et
inutile.
OU mettre la fonction ?
Habituellement, un programme est subdivisé en
plusieurs fichiers que l'on classe en deux catégories :
1. Les fichiers de code (les .cpp )
2. Les fichiers d'en-téte (les .hpp )
Généralement, on met le prototype de la fonction dans
un .hpp etladéfinition dans le .cpp
Pour les fonctions templates, c'est différent.
TOUT doit obligatoirement se trouver dans le
fichier .hpp__, sinon votre programme ne
pourra pas compiler.
Je le répéte car c'est une erreur classique : le
prototype ET la définition d'une fonction
template doivent obligatoirement se trouver
dans un fichier d'en-téte.Tous les types sont-ils utilisables ?
Le compilateur génére toutes les fonctions
nécessaires. Cependant, il y a quand méme une
contrainte ici : le type que l'on passe a la fonction doit
posséder un operator>
Par exemple, on ne peut pas utiliser cette fonction
avecun Personnage ouun Magicien des
chapitres précédents : ils ne possédent pas de
surcharge de > . Tant mieux, puisque prendre le
maximum de deux personnages n‘a pas de sens !
Les contraintes dépendent des fonctions que vous
écrivez :
* Si vous utilisez 'opérateur + dans la fonction,
alors il faut que l'objet passé en argument
surcharge cet opérateur.
« Si vous effectuez une copie dans la fonction,
alors l'objet doit posséder un constructeur de
copie, etc.
Découvrez des fonctions plus os
compliquées
Vous avez appris a écrire une fonction qui calcule la
moyenne d'un tableau. A nouveau, les operations a
effectuer sont les mémes quel que soit le type
contenu.Ecrivons donc cette fonction sous forme de template.
Voici ma version :
cpp
DMG ane) CS iran Fe BED)
{
Sette
¢ i(0); i au
lieu d'un tableau statique ;
¢ écrire une fonction template renvoyant un
nombre aléatoire d'un type danné
Spécialisez la fonction
template
Pour l'instant, nous n'avons essayé la fonction
maximum() qu'avec des types de base. Essayons-la
donc avec une chaine de caracteres
goesint main()
{
hd
DeSsut aah
Le résultat de ce petit programme est
eis
[ema -4
Uopérateur < pour les chaines de caractéres
compare suivant l'ordre lexicographique.
Mais imaginons (comme précédemment) que le critére
de comparaison qui nous intéresse soit la longueur de
la chaine. Cela se fait en spécialisant la fonction
template.
Comprenez le principe de la spécialisation
La spécialisation emploie la syntaxe suivante
Dae U a ven iets Uys
eet)
Vous remarquerez deux chosesVous remarquerez deux choses
1. La premiére ligne ne comporte aucun type entre
< et >
2. Le prototype de la fonction utilise cette fois le
type que l'on veut, et non plus le type générique
T
Avec cette spécialisation, on obtient le comportement
voulu :
cpp
int main()
af
cout
Ce qui donne:
Le plus grand est: eleph
La seule difficulté de la spécialisation est la
syntaxe, qui commence par la ligne
template<> . Si vous vous souvenez de
cela, vous savez tout.
Vous pouvez évidemment spécialiser la
fonction pour plusieurs types différents. Il vous
faudra alors créer une spécialisation par type.Pensez a I'ordre des fonctions
Pour pouvoir compiler et avoir le comportement voulu,
votre programme devra étre organisé d'une maniére
speciale. II faut respecter un ordre particulier :
1. La fonction générique
2. Les fonctions spécialisées.
Lordre est essentiel. Lors de la compilation, le
compilateur cherche une fonction spécialisée. S'il n'en
trouve pas, alors il utilise la fonction générique
déclarée au-dessus.
Réalisezlesclasses template v
Voyons maintenant comment réaliser des classes
template, c'est-a-dire des classes dont le type des
arguments peut varier.
Prenons un exemple : lorsque l'on veut dessiner des
choses a l'écran, on utilise quelques formes de base
qui servent a décomposer les objets plus complexes.
Lune de ces formes est le rectangle.
Voici les propriétés (ou les attributs) d'un rectangle :
1. Quatre cétés.
2. Une surface.
3. Un périmétre.
Les deux derniers éléments peuvent étre calculés si
l'on connait sa longueur et sa largeur.Maintenant, voyons quelles actions on peut associer a
un rectangle (vérifier si un point est contenu dans le
rectangle, déplacer le rectangle...).
On pourrait donc modéliser notre classe comme ceci :
Rectangle
# gauche
# droite
# haut
# bas
+ deplacer()
contient()
On considére ici un rectangle paralléle aux
bords de l'écran, ce qui permet de simplifier les
positions en utilisant un seul et unique nombre
par céte.
Indiquez le type des attributs
Maintenant que nous avons modélisé la classe, il est
temps de réfléchir aux types des attributs, en
l'occurrence la position des cétés :1. Option 1: si l'on veut avoir une bonne précision,
il faut utiliser des double oudes float
2. Option 2 : si par contre on consideére que, de
toute fagon, l'écran est composé de pixels, on
peut se dire que l'utilisation d' int est
largement suffisante.
Les deux options sont possibles, et on peut trés bien
avoir besoin des deux approches dans un seul et
méme programme
Et c'est la que vous devriez tous me dire : "Mais alors,
utilisons donc des templates !".
Vous avez bien raison. Nous allons écrire une seule
classe qui pourra étre instanciée par le compilateur
avec différents types
Créez la classe
Je suis sr que vous connaissez la syntaxe méme si je
ne vous I'ai pas encore donnée. Comme d'habitude, on
déclare un type générique T , puis on déclare notre
Classe :
Notre type générique est reconnu par le compilateur
l'intérieur de la classe. Utilisons-le donc pour déclarer
nos quatre attributstemplate
Pree ar sant
Dae
T m_droite;
Samet
aan
Voila. Il ne nous reste plus qu'a écrire les méthodes.
Ecrivez les méthodes
Les fonctions les plus simples a écrire sont
certainement les accesseurs, qui permettent de
connaitre la valeur des attributs. La hauteur d'un
rectangle est évidemment la différence entre la
position du haut et la position du bas. Comme vous
vous en doutez, cette fonction est template, puisque
le type de retour de la fonction sera un T
Ecrivez une premiere méthode
Nous pouvons donc écrire la méthode suivante :
c_cpp
fe Er
Era ae taehauteur()
Car Teh oe oo
m_gauche;
Cae
Partha
fe
Vous remarquerez qu'il n'y a pas besoin de
redéclarer le type template T juste avant la
fonction membre, puisque celui que nous avons
déclaré avant la classe reste valable pour tout
ce qui se trouve a l'intérieur.
Et si je veux mettre le corps de ma fonction a
l'extérieur de ma classe ?
Bonne question. On prend souvent I'habitude de
separer le prototype de la définition. Et cela peut se
faire aussi ici. Pour cela, on mettra le prototype dans la
classe, et la définition a |'extérieur ; mais il faut
indiquer a nouveau qu'on utilise un type variable T
cppOyu
s Rectangle{
Pil a ets
T hauteur() const;
ctangle: :hauteur() const
eee ree
Vous remarquerez aussi l'utilisation du type template
dans le nom de la classe, puisque cette fonction sera
instanciée de maniére différente pour chaque T
Souvenez-vous que tout doit se trouver dans le
fichier .hpp !
Ecrivez une fonction un peu plus complexe
Une des fonctions que nous voulions écrire est celle
permettant de vérifier si un point est contenu dans le
rectangle ou pas. Pour cela, on doit passer un
point $$$(2x; y)$$$ en argument a la fonction
Le type de ces arguments doit évidemment étre T
de sorte que l'on puisse comparer les coordonnées
sans avoir de conversions.cpp
Fees a a
eae et
ab teed
see ae oe eS
return (x >= m_gauche) && (x <= m_droite)
Sa meat Mario
Vous remarquerez 4 nouveau l'absence de redéfinition
dutype T Quoi, je me répete ? C'est srement que
cela devient clair pour vous. @
Traitez le cas du constructeur
Il ne nous reste plus qu'a traiter le cas du constructeur.
A nouveau, rien de bien compliqué, on utilise
simplement le type T défini avant la classe :
s Rectangle{
re eta
[tee RAG ce Pe PD)
aera tes
(droite),
Catach
GEEt comme pour toutes les autres méthades, on peut
définir le constructeur a |'extérieur de la classe. Vous
étes bientdt incollable sur le sujet, je vous laisse donc
essayer seu
On pourrait ajouter une fonction appelée dans
le constructeur, qui vérifie que le haut se trouve
bien au-dessus du bas, et de méme pour droite
et gauche.
Finalement, voyons comment utiliser cette classe.
Instanciez une classe template
\I fallait bien y arriver un jour !
Comment crée-t-on un objet de la classe
Rectangle ?
En fait, je suis sdr que vous le savez déja. Cela fait
longtemps que vous créez des objets a partir de la
classe template vector ou map
Sil'on veut un Rectangle composéde double ,
on devra écrire :
cppFinalement, voyons comment utiliser cette classe.
Instanciez une classe template
I fallait bien y arriver un jour !
Comment crée-t-on un objet de la classe
Rectangle ?
En fait, je suis sir que vous le savez déja. Cela fait
longtemps que vous créez des objets a partir de la
classe template vector ou map
Sil'onveut un Rectangle composéde double ,
on devra écrire :
int main()
a
Pree a See 0 oem a Cone 2
Lutilisation des fonctions se fait ensuite comme
d'habitude :
cpp
int main()
rt
[tera shee een tea) 2
footie [Link]() abPour terminer ce chapitre, je vous propose d'ajouter
quelques méthodes a cette classe.
Je vous parlais d'une méthode deplacer() qui
change la position du rectangle. Essayez aussi d'écrire
les méthodes surface() et perimetre()
En résumé v
« Les templates sont utilisés pour créer différentes
versions d'une fonction ou d'une classe pour des
types différents.
¢ Pour créer une fonction ou une classe template,
il faut déclarer un type générique en utilisant la
syntaxe template
¢ Pour utiliser une fonction ou une classe template,
on indique le type désiré entre les chevrons <
et |
¢ ll est possible de spécialiser les templates pour
leur imposer un comportement particulier pour
certains types.
Maintenant, vous pouvez simplifier (ensemble de vos
programmes en C++ grace aux templates ; ils seront
vos alliés pour éviter la duplication du code. Sinon, jai
une bonne nouvelle : cest bientét la fin du cours. Allez
,un dernier "petit" chapitre, et je vous laisse tranquille.
Si l'apprentissage du C++ vous a plu, je vous propose
de vous présenter comment aller encore plus loin !