Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Infoal Compressed

Télécharger au format pdf ou txt
Télécharger au format pdf ou txt
Vous êtes sur la page 1sur 165

Séance 4

Séance 4

Notion d'objet, de classe,


constructeurs destructeurs, new,
delete, this...

3
INFO 9-10

Séance 4

LES CLASSES
Points abordés :


Déclaration d’une classe.

Encapsulation et indicateurs
de visibilité.

Surcharge de méthodes.

Fichier d’entête.

Implantation de la classe.

Fichier de corps.

Instanciation d’une classe.

Accès aux membres d’une
classes.

Exemple : la classe Position.
Les classes Séance 4

Les classes : définitions et motivations

 En faisant le parallèle avec le C, on peut dire qu’une classe est


une structure à laquelle on ajoute des fonctions permettant de
gérer cette structure.
 Outre l'ajout de ces fonctions, il existe deux grandes nouveautés
par rapport aux structures du C :
• les membres de classe, qu'ils soient des variables ou des
fonctions, peuvent être privés c'est-à-dire inaccessibles en
dehors de la classe (par opposition aux membres publics d'une
structure C où tous ses membres sont accessibles).
• une classe peut être dérivée. La classe dérivée hérite alors de
toutes les propriétés et fonctions de la classe mère. Une classe
peut d'ailleurs hériter de plusieurs classes simultanément.

Les classes Séance 4

Déclaration d’une classe


Le mot clé class

 Une classe est déclarée grâce au mot clé class. Elle peut
s’effectuer de deux manières :

 Simplement marquer
class NomDeLaClasse;
On déclare simplement l’existence d’une classe et la
description de ses membres sera faite un peu plus loin.

 Ecrire
class NomDeLaClasse { … };
On déclare l’existence de la classe et on décrit (entre les
accolades) les membres qui la composent.
Les classes Séance 4

Déclaration d’une classe


Attribut et méthode

 La liste des attributs et des méthodes sont placés à l’intérieur de


l’accolades :

 Pour les attributs :


Il s’agit d’écrire le type de l’attribut suivi de son nom suivi
d’un point-virgule (comme la déclaration des champs d’une
structure ou d’une union).

 Pour les méthodes :


Il faut écrire la signature (prototype) suivi d’un point-virgule,
autrement dit le type de retour, le nom de la fonction et la
liste des arguments entre parenthèses

Les classes Séance 4

Déclaration d’une classe


Le nommage – Rappel des règles

 Le système de nommage des classes, des attributs et des


méthodes suit les mêmes règles que pour le langage C

 les noms s’écrivent en utilisant les lettres, les chiffres et le


caractères soulignés
 les noms ne peuvent pas être un mot clé du C++ ;
 les noms ne peuvent pas commencer par un chiffre.
Les classes Séance 4

Déclaration d’une classe


Le nommage – Les conventions

 Il est d’usage de respecter les conventions ci-dessous :

 Les noms des classes :


 un nom simple s’écrit en commençant par une lettre
majuscule (les autres lettres étant en minuscule) ;
 pour un nom complexe, chaque terme commence par une
lettre majuscule (les autres lettres étant écrites en
minuscules).

 Les noms des attributs et des méthodes :


 On reprend les mêmes règles de nommage à la différence
que la première lettre est une minuscule.

Les classes Séance 4

Déclaration d’une classe


Exemple

Attributs

class HeroDuJeu
{
int nombreDePointsDeVie;
bool invincible;
void seDeplacer (int direction);
Position getPosition ();
};

Méthodes
Les classes Séance 4

Encapsulation et indicateur de visibilité


Principe d’encapsulation

 Le principe d’encapsulation consiste à cacher les données à


l’intérieur des objets et de ne permettre leur accès (en lecture ou
en écriture) au moyen de méthodes particulières appelées
accesseurs.

 Cette encapsulation a pour but de limiter la propagation de


l’organisation physique des données au sein du code.

Les classes Séance 4

Encapsulation et indicateur de visibilité


Les indicateurs de visibilité

 Ce principe d’encapsulation se traduit par l’utilisation


d’indicateurs de visibilité pour spécifier :
 les membres privés qui doivent être cachés ;
 les membres publics qui doivent être accessibles aux autres
objets.

Par défaut, dans une classe les membres sont privés, alors que
dans une struct les membres sont publics (c'est une des principales
différences entre struct et class).
Les classes Séance 4

Encapsulation et indicateur de visibilité


Les indicateurs de visibilité en C++

 En C++, on définit des sections privées et publiques grâce aux


mots clés private et public :

 Pour déclarer une section privée, on écrit simplement


« private : », cette section s’étend jusqu’à la fin de la
déclaration de la classe ou jusqu’au début de la section
suivante.
 Pour déclarer une section publique, on écrit « public : » et la
section s’étend jusqu’à la fin de la déclaration de la classe ou
jusqu’au début de la section suivante.

 Il existe aussi (moins usité) le mot clé protected : cette partie


sera accessible aux fonctions membres de la classe mais aussi
aux fonctions membres des classes dérivées

Les classes Séance 4

Encapsulation et indicateur de visibilité


Les indicateurs de visibilité en C++ – Exemple

 Dans l’exemple ci-dessous :


 la section privée s’étend de « private : » jusqu’à la ligne avant
« public : » ;
 La section publique s’étend de « public : » jusqu’à la fin de la
déclaration de la classe.

class nomDeLaClasse
{
private :
// Déclaration des membres privés

public :
// Déclaration des membres publics
};
Les classes Séance 4

Surcharge de méthodes
Principe

 En C++, il est désormais possible de surcharger les fonctions.

 Il en va de même pour les méthodes : au sein d’une classe, il est


possible de fournir plusieurs versions d’une même méthode
pourvu que le nombre et/ou le type des arguments changent (le
type de retour devant rester le même).

 Cependant si la méthode à surcharger comporte des arguments


par défaut et que les autres versions de la méthode ne varient que
par un nombre inférieur d’arguments, le compilateur retournera
une erreur.

Les classes Séance 4

Surcharge de méthodes
Exemple de méthodes avec des arguments par défaut

 Si on instancie la classe C en un objet c et qu’on invoque la


méthode getInformation sans passer d’argument, le compilateur
ne saura pas quelle méthode choisir …

class C
{
private :

// déclaration des attributs

public :

/* Méthode qui retourne l’information par défaut */


int getInformation ();

/* Si on ne passe pas d’argument type vaut DEFAUT, une


constante prédéfinie */
int getInformation (int type=DEFAUT);
};
Les classes Séance 4

Le fichier d’entête
La forme du fichier

 Après avoir spécifié l’existence de la classe et listé ses membres,


nous obtenons un fichier d’entête que nous pourrions nommer
nomDeLaClasse.h (il s’agit d’une convention qu’il est conseillée
de suivre).

class NomDeLaClasse
{
private :

type attribut1;
type attribut2;

public :

typeDeRetour méthode1 (type argument …);


typeDeRetour méthode2 (type argument …);
};

Les classes Séance 4

Le fichier d’entête
La forme du fichier

 Ce fichier contient généralement des directives du préprocesseur


afin d’éviter la déclaration multiple de classes qui peuvent
entraîner des erreurs de compilation.

 Lorsqu’on construit une classe, celle-ci est susceptible d’être


utilisée à différents endroits, ce qui implique de charger plusieurs
fois le fichier d’entête : si la déclaration de la classe a déjà été
chargée une fois, un second chargement provoque une erreur.
Les classes Séance 4

Le fichier d’entête
La forme du fichier

 Comme il n’est pas aisé de déterminer l’endroit où ce chargement


s’effectuera en premier, on charge le préprocesseur de ce travail
de la manière suivante :

 tester (#ifndef) si une variable n’existe pas


 Si elle n’existe pas :
– la créer (#define) (de cette manière, le test sera faux
les fois suivante) ;
– charger la déclaration de la classe.
 Si elle existe, sauter directement à la directive #endif.

Les classes Séance 4

Le fichier d’entête
La forme du fichier d’entête

 Finalement, on obtient un fichier d’entête qui a la forme ci-dessous


:

#ifndef _NomDeLaClasse
#define _NomDeLaClasse
class NomDeLaClasse
{
private :

type attribut1;
type attribut2;

public :

typeDeRetour méthode1 (type argument …);


typeDeRetour méthode2 (type argument …);
};
#endif
Les classes Séance 4

Implantation de la classe
Le lien entre la déclaration et l’implantation d’une méthode

 Après avoir écrit le fichier d’entête, nous nous intéressons au


fichier de corps contenant l’implantation des méthodes de la
classe.

 Comme cette implantation s’effectue (souvent) dans un fichier


distinct, il est nécessaire de mettre en place un mécanisme pour
permettre au compilateur de faire le lien entre la déclaration
d’une méthode appartement à une classe et son implantation.

Les classes Séance 4

Implantation de la classe
L’opérateur de portée

 La solution consiste à utiliser un opérateur de portée, symbolisé


par « :: » afin de pouvoir indiquer la classe à laquelle appartient la
méthode.

 La syntaxe d’implantation d’une méthode est alors la suivante :

typeDeRetour NomDeLaClasse::méthode1 (type argument …)


{
// Code de la méthode 1
}

typeDeRetour NomDeLaClasse::méthode2 (type argument …)


{
// Code de la méthode 2
}
Les classes Séance 4

Implantation de la classe
L’opérateur de portée

 Cet opérateur de portée permet donc de distinguer des méthodes


qui portent le même nom mais qui appartiennent à des classes
différentes.

typeDeRetour NomDeLaClasse1::méthode (type argument …)


{
// Code méthode appartenant à NomDeLaClasse1
}

typeDeRetour NomDeLaClasse2::méthode (type argument …)


{
// Code méthode appartenant à NomDeLaClasse2
}

Les classes Séance 4

Le fichier de corps
L’opérateur de portée

 Après avoir implanté les différentes méthodes, nous obtenons un


fichier de corps que nous pourrions nommer
nomDeLaClasse.cpp (convention).
 Ce fichier contient au moins un include du fichier d’entête
correspondant.

#include "nomDeLaClasse.h"

typeDeRetour NomDeLaClasse1::méthode (type argument …)


{
// Code méthode appartenant à NomDeLaClasse1
}

typeDeRetour NomDeLaClasse2::méthode (type argument …)


{
// Code méthode appartenant à NomDeLaClasse2
}
Les classes Séance 4

Instanciation d’une classe


Le principe

 Le code d’une méthode ou d’une fonction reprend les éléments du


langage C (la déclaration des variables, les structures de contrôle,
les appels de fonctions …)

 Il est désormais possible de créer des instances des classes (des


objets), d’invoquer des méthodes sur ces objets, de les passer en
paramètre des fonctions comme n’importe quelle variable …etc

Les classes Séance 4

Instanciation d’une classe


La création statique d’un objet

 Pour déclarer une instance d’une classe, il suffit de taper :

NomDeLaClasse nomDeLaVariable;
 On dispose alors d’un objet appelé nomDeLaVariable où tous ses
attributs ont été initialisés à leur valeur par défaut :
 0 pour les attributs de type entier ;
 0.0 pour les attributs de type flottant ;
 false pour les attributs de type bool.

 Remarque : le nom de la variable suit généralement les


règles de nommage du nom de l’attribut (il commence donc par
une minuscule).
Les classes Séance 4

Instanciation d’une classe


La création dynamique d’un objet
 Pour déclarer une instance d’une classe, il suffit de taper :

NomDeLaClasse* nomDuPointeurSurLaVariable;

nomDuPointeurSurLaVariable = new NomDeLaClasse ;

 ATTENTION : Tout objet créé dynamiquement devra impérativement être


détruit à la fin de son utilisation grâce au mot clé delete. Dans le cas
contraire, une partie de la mémoire (celle utilisée par les objets créés
dynamiquement) ne sera pas libérée à la fin de l'exécution du programme…

 Les objets créés de façon statique n'ont pas besoin d'être détruits, ils
sont automatiquement supprimés lorsque le programme ne fonctionne plus
dans la portée dans laquelle ils ont été définis

Les classes Séance 4

Accès aux membres d’une classe


L’utilisation de l’objet

 Il est possible d’accéder aux membres publics d’un objet en


utilisant l’opérateur « point » pour les objets statiques.

NomDeLaClasse nomDeLaVariable;

nomDeLaVariable.attributPublic = valeurs;
nomDeLaVariable.méthode (argument);
 Si ce membre est privé, cela provoque une erreur de compilation.
 Il est possible d’accéder aux membres publics d’un objet en
utilisant l’opérateur «flêche» pour les objets dynamiques.

NomDeLaClasse* nomDuPointeurSurLaVariable;

nomDuPointeurSurLaVariable->attributPublic = valeurs;
nomDuPointeurSurLaVariable->méthode (argument);
Les classes Séance 4

Exemple : la classe Position


Description de la classe

 Cette classe permet de manipuler plus facilement les


coordonnées d’un personnage sur une aire de jeu (exemple de
jeu : PACMAN). Elle comporte les éléments suivants :
 2 attributs privés de type int, appelés abscisse et ordonnée ;
 4 accesseurs pour accéder aux attributs en lecture (2 getter)
et en écriture (2 setter) ;
 1 méthode « toString » qui retourne sous forme d’une chaîne
de caractères (le type string étant défini dans la bibliothèque
standard STL) la valeur des différents attributs de Position
afin de faciliter le débogage.

Les classes Séance 4

Exemple : la classe Position


Le code à placer dans le fichier d’entête position.h
#ifndef _POSITION
#define _POSITION

#include <string>
using namespace std;

#define ABSCISSE 0
#define ORDONNEE 1

class Position
{
private :
int coordonnees [2];

public :
int getCoordonnee (int _type);
void setCoordonnee (int _type, int _valeur);
void incrementerCoordonnee (int _type);
void incrementerCoordonnee (int _type, int _valeur);
void decrementerCoordonnee (int _type);
void decrementerCoordonnee (int _type, int _valeur);
string toString ();
};

#endif
Les classes Séance 4

Exemple : la classe Position


Le code à placer dans le fichier d’entête position.cpp
#include <position.h>
#include <stdio.h>

int Position::getCoordonnee (int _type)


{
return coordonnee [_type];
}

void Position::setCoordonnee (int _type, int _valeur)


{
coordonnee [_type] = _abscisse;
}

void Position::incrementerCoordonnee (int _type)


{
coordonnee [_type] ++;
}

void Position::incrementerCoordonnee (int _type, int _valeur)


{
coordonnee [_type] += _valeur;
}

Les classes Séance 4

Exemple : la classe Position


Le code à placer dans le fichier d’entête position.cpp
#include <position.h>
#include <stdio.h>

void Position::decrementerCoordonnee (int _type)


{
coordonnee [_type] --;
}

void Position::incrementerCoordonnee (int _type, int _valeur)


{
coordonnee [_type] -= _valeur;
}

string Position::toString ()
{
char chaine [50];
sprintf (chaine, "Position (%d,%d)", abscisse, ordonnee);
return *(new string (chaine));
}
Les classes Séance 4

Exemple : la classe Position


L’utilisation de Position – Le fichier main
#include <position.h>

#include <iostream>
using namespace std;

int main (int argc, char** argv)


{
Position p;

cout << p.toString () << endl;

p.setCoordonnee (ABSCISSE, 20);


p.setCoordonnee (ORDONNEE, 40);

cout << p.toString () << endl;


}

Séance 4

La forme canonique de Coplien


Points abordés :


Les attributs de type
pointeur.

La classe Personne

Le constructeur.

Le constructeur par
recopie.

Le destructeur.

L ’opérateur
d’affectation.
La forme canonique de Coplien Séance 4

Les attributs de type pointeur


Les méthode par défaut de C++

 Le langage C++ propose un certain nombre de méthodes par


défaut, notamment :
 un constructeur par défaut qui se contente d’initialiser les
attributs à la valeur nulle (il n’effectue pas d’allocation
dynamique s’il existe un attribut de type pointeur) ;
 un constructeur par recopie par défaut qui se limite à copier
les attributs de la source vers la cible ;
 un destructeur par défaut qui se cantonne à détruire l’objet
sans libérer la mémoire dans le cas où une allocation
dynamique aurait été effectué dans un constructeur.
 un opérateur d’affectation par défaut qui se contente de copier
les attributs de la source vers la cible.

La forme canonique de Coplien Séance 4

Les attributs de type pointeur


Le problème du constructeur par défaut

 Compte tenu de ces caractéristiques, imaginons maintenant un


objet qui dispose d’un attribut p de type pointeur.

 On créé une instance a de cet objet, sans redéfinir le constructeur,


le pointeur p est initialisé à la valeur nulle donc si une méthode
cherche à l’utiliser sans précaution cela conduit à une erreur à
l’exécution
La forme canonique de Coplien Séance 4

Les attributs de type pointeur


Le problème du destructeur par défaut

 Maintenant imaginons que le constructeur soit correctement


redéfini.

 On créé une instance a de cet objet, sans redéfinir le destructeur,


le pointeur p pointe sur une zone mémoire qui a été allouée à la
construction.

 Si on détruit cet objet en utilisant le destructeur par défaut, la zone


de mémoire ne sera pas libérée et on se trouvera en présence
d’une fuite de mémoire.

La forme canonique de Coplien Séance 4

Les attributs de type pointeur


Le problème de la recopie par défaut

 Maintenant si on considère que le constructeur et le destructeur


par défaut sont correctement implantés, nous pouvons encore
avoir le problème suivant.

 On créé une instance a de cette classe, nous avons donc la


situation ci-dessous :

p=adresse

Objet a
La forme canonique de Coplien Séance 4

Les attributs de type pointeur


Le problème de la recopie par défaut

 Si on créé un objet b par recopie de a, l’attribut p de b contiendra


la même valeur que l’attribut p de a, donc les deux objets
pointeront sur la même zone mémoire

p=adresse p=adresse

Objet a Objet b

La forme canonique de Coplien Séance 4

Les attributs de type pointeur


Le problème de la recopie par défaut

 Si le destructeur est correctement implanté et que nous détruisons


l’objet a, la zone mémoire pointée par p de a est détruite donc le
pointeur p de b pointe sur une zone désallouée

p=adresse p=adresse

Objet a Objet b
La forme canonique de Coplien Séance 4

Les attributs de type pointeur


Le problème de l’opérateur d’affectation par défaut

 Si les 3 méthodes évoquées jusqu’à maintenant sont redéfinie,


nous pouvons encore rencontrer un problème dont les symptômes
sont assez semblables à ceux du constructeur par recopie avec
l’opérateur d’affectation.

 Celui-ci est appelé lorsque les objets a et b existent déjà mais que
nous effectuons en cours d’exécution a = b.

 Si l’opérateur = n’est pas redéfini, le pointeur p de a pointe sur la


même zone mémoire que celui de b.

La forme canonique de Coplien Séance 4

Les attributs de type pointeur


La forme de Coplien

 Compte tenu de ces problèmes, une implantation canonique des


classes, dite « forme de Coplien » a été définie dans le cas où
celles-ci comporteraient des attributs de type pointeur.

 Elle consiste à redéfinir :


 le constructeur ;
 le destructeur ;
 le constructeur par recopie ;
 l’opérateur d’affectation.
La forme canonique de Coplien Séance 4

La classe Personne
Description du cas d’étude

 Pour décrire l’implantation des différentes méthodes à redéfinir,


nous allons partir d’une classe Personne comportant un pointeur
sur un char.

class Personne
{
private :
char* nom;

public :
Personne ();
Personne (char*);
Personne (const Personne&);
~Personne ();
Personne& operator= (const Personne&);
};

La forme canonique de Coplien Séance 4

Le constructeur
Le principe

 Le constructeur est redéfini afin d’ajouter les instructions


nécessaire à l’allocation de la mémoire pointée par l’attribut
modèle.

 Dans un premier temps, nous allons nous contenter d’utiliser


l’instruction malloc (étudié en langage C), nous étudierons
l’utilisation de new dans un autre module.
La forme canonique de Coplien Séance 4

Le constructeur
Le code

Personne::Personne ()
{
nom = (char*) malloc (strlen ("inconnu")+1);
if (nom != NULL)
strcpy (nom, "inconnu");
}

Personne::Personne (char* _nom)


{
nom = (char*) malloc (strlen (_nom)+1);
if (nom != NULL)
strcpy (nom, _nom);
}

La forme canonique de Coplien Séance 4

Le constructeur par recopie


Le principe et le code

 Nous redéfinissons le constructeur par recopie qui va allouer une


nouvelle zone de mémoire de manière à recopier les informations
depuis la zone de mémoire désigné par le pointeur de l’objet
source.

Personne::Personne (const Personne& P)


{
nom = char* malloc (strlen (P.nom)+1);
if (nom != NULL)
strcpy (nom, P.nom);
}
La forme canonique de Coplien Séance 4

Le destructeur
Le principe et le code

 Nous redéfinissons le destructeur afin d’ajouter les instructions


nécessaire à la libération de la zone mémoire désignée par le
pointeur et ainsi éviter les fuites de mémoire.
 A ce niveau, nous utiliserons l’instruction free, l’instruction delete
sera étudiée dans un autre module de cours.

Personne::~Personne ()
{
if (nom != NULL)
free (nom);
}

La forme canonique de Coplien Séance 4

L’opérateur d’affectation
Le principe et le code

 Nous redéfinissons l’opérateur d’affection pour les mêmes raisons


que celles qui ont conduit à la redéfinition du constructeur par
recopie.

 Généralement, on retrouve un code assez similaire à celui du


constructeur par recopie.

Personne& Personne::operator= (const Personne& P)


{
if (this != &P)
{
if (nom != NULL) free(nom);
nom = (char*) (strlen (P.nom)+1);
if (nom != NULL) strcpy (nom,P.nom);
}
return *this;
}
Séance 4

Autres caractéristiques
Points abordés :


Le pointeur courant this

Opérateur new et delete

Les attributs statiques.

Les méthodes statiques.

Autres caractéristiques Séance 4

Le pointeur courant this

 Le mot clé this permet de désigner l'objet dans lequel on se


trouve, c'est-à-dire que lorsque l'on désire faire référence dans
une fonction membre à l'objet dans lequel elle se trouve, on utilise
this.

 L'objet courant this est en réalité une variable système qui permet
de désigner l'objet courant. Cette variable est passée en tant que
paramètre caché de chaque fonction membre.

 Ainsi, lorsque l'on désire accéder à une donnée membre d'un


objet à partir d'une fonction membre du même objet, il suffit de
faire précéder le nom de la donnée membre par this->.
Autres caractéristiques Séance 4

Le pointeur courant this

 Exemple :

class Toto{
private :
int age;
char sexe[16];
public :
void DefineTotoAge(int);
};

void Toto::DefineTotoAge(int age){


this->age = age;
}

Autres caractéristiques Séance 4

Les opérateurs new et delete

 Les instructions malloc et free allouent et libèrent des mémoires


indépendamment du fait qu’elles contiennent des objets car ces
instructions sont issues du langage C.

 Il est donc préconisé d’utiliser :


 l’opérateur new à la place de l’instruction malloc pour créer
dynamiquement des objets (par opposition à la création
statique d’objet, voir par ailleurs) ;
 l’opérateur delete à la place de l’instruction free détruire les
objets (libérer la mémoire allouée).

 Il est aussi conseillé d’utiliser les opérateurs new [ ] et delete [ ]


pour créer dynamiquement et détruire explicitement des tableaux
d’objets (voir par ailleurs).
Autres caractéristiques Séance 4

L’opérateur new
L’instanciation dynamique d’objet

 new est un mot clé du langage C++.

 Cet opérateur permet de créer dynamiquement une instance


d’une classe selon la syntaxe ci-dessous :

NomClasse* nomInstance = new NomClasse;

 En utilisant cette écriture, l’opérateur new


 appelle le constructeur par défaut ;
 renvoie directement un pointeur sur NomClasse ;
 lance l’exception bad_alloc en cas d’échec. (voir par
ailleurs)

Autres caractéristiques Séance 4

L’opérateur new
Le passage de paramètre

 Contrairement à l’instruction malloc, nous pouvons passer des


paramètres afin d’initialiser les attributs de l’objet instancié.

 Nous pouvons par exemple écrire l’instruction suivante :

NClass* nInstance = new NClasse (arg1,…);

 L’opérateur new appelle le constructeur qui prend en paramètre


les arguments que nous avons passés à l’opérateur new.
Autres caractéristiques Séance 4

L’opérateur new
L’utilisation de l’opérateur new avec des types simples

 Il est également possible d’utiliser l’opérateur new avec des types


simples comme char, double ou int.

 Nous pouvons alors écrire :

char* pChar = new char ('p');


int* pInt = new int (100);

 Ces opérateurs new vont donc construire des variables de type


char ou int, les initialiser avec la valeur passée en paramètre puis
retourner un pointeur sur ces variables.

Autres caractéristiques Séance 4

L’opérateur delete
La destruction explicite d’objets

 Un objet créé de façon dynamique par l’opérateur new doit être


détruit de façon explicite par l’opérateur delete.

 Si nous disposons d’un pointeur p sur une classe Position, nous


écrivons simplement :
delete p;
 Cet opérateur delete :
 appelle le destructeur de la classe Position (pour effectuer
d’autres traitements comme la libération de l’espace mémoire
occupé par les attributs)
 désalloue la mémoire occupée par cet objet.
Autres caractéristiques Séance 4

L’opérateur new [ ]
La création de tableaux dynamiques

 L’opérateur new permet de créer dynamiquement un objet,


l’opérateur new [ ] permet donc de créer dynamiquement un
tableau d’objets.

 Si nous souhaitons par exemple créer un tableau d’objets de type


Position, nous pourrons taper l’instruction suivante :
Position* p = new Position [10];

 Cet opérateur réservera alors la place nécessaire pour stocker 10


objets de type Position puis il invoquera le constructeur par
défaut.

Autres caractéristiques Séance 4

Les attributs statiques


Le principe

 Un attribut statique est un attribut qui est partagé par l’ensemble


des instance d’une même classe.

 D’un point de vue programmation, cet attribut est précédé du mot


clé static.

class A
{
static int compteur;
};
Autres caractéristiques Séance 4

Les attributs statiques


Le principe

 Cet attribut peut être manipulé sans avoir à créer un objet, on peut
donc y accéder de deux manières :
 par l’intermédiaire de la classe :

A::compteur = 10;
 par l’intermédiaire d’un objet :

A a;
a.compteur=10;

Autres caractéristiques Séance 4

Les attributs statiques


Le principe

 Comme cet attribut est commun à tous les objets, si un objet


modifie la valeur de cet attribut, les autres objets verront la
modification

A a;
A b;

a.compteur=10;
b.compteur=20;
// a voit compteur=20
Autres caractéristiques Séance 4

Les attributs statiques


Exemple

 Cet attribut statique peut être utilisé pour compter le nombre


d’objets instanciés

Class A int A::compteur = 0;


{
static int compteur; A::A (const &A)
A (); {
A (const &A); …
~A (); compteur++
}; }

A::A () A::~A ()
{ {
… …
compteur++ compteur--;
} }

Autres caractéristiques Séance 4

Les méthodes statiques


Le principe

 A l’instar de l’attribut statique, une méthode statique ne nécessite


pas de créer un objet pour pouvoir être utilisée.

 D’un point de vue programmation, cet attribut est également


précédé du mot clé static.

class A {
static int compteur;

public :
static int getCompteur();
static void setCompteur(int _compteur);
};
EXERCICE 1 Séance 4

Modificateurs d’accès

→ Modifier votre classe pour qu on puisse créé un objet ainsi :


Rectangle rect(12,10);
→ Assurez-vous que vous respectez la forme canonique (ajout
65 du constructeur par recopie et de l’opérateur d’affectation)
INFO
→ redéfinir l’opérateur ++ pour 9-10classe Rectangle
votre
→ Projet final : un main + un source + un header

EXERCICE 2 Séance 4

Date

Projet final : un main + un source + un header


66
INFO 9-10
EXERCICE 3 Séance 4

Date (suite)

67
INFO 9-10

EXERCICE 3 Séance 4

Date (suite)
Un header possible et le main
Séance 4

EXERCICE 4
votre classe String :
constructeur, destructeur etc ...
En absence d’une classe représentant les chaînes de caractères en C++, vous allez
développer la votre. Enfin, vous allez développer une classe, appelée String (souvenez-
vous que C++ est sensible à la casse), qui, au fur et à mesure des solutions implantées,
ressemblera à une vraie classe, quoique un peu limitée, pour gérer les chaînes.
Commencez par déclarer une classe appelée String qui aura un unique attribut privé : un
tableau de caractères. Implantez les méthodes nécessaires pour avoir cela :

69
INFO 9-10

Séance 4

EXERCICE 4
votre classe String :
constructeur, destructeur etc ...
Implémentez également les méthodes suivantes :

Exemple :

Les fonctions suivantes pourraient vous être utiles :

70
INFO 9-10
Séance 4

Séance 4


Les classes : déclaration , instanciation


La forme canonique de Coplien : constructeur,
destructeur …


Autres caractéristiques : this, new, delete, static ...
INFO 9-10 :
Langage C++

Option « Systèmes Informatiques embarqués »


Alexis LANDRAULT
Alexis.Landrault@uca.fr

1 INFO 9-10: langage C++


Séance 8

Séance 8

Surcharge d'opérateurs,

Template,

exceptions

3
INFO 9-10

La surcharge des opérateurs


Points abordés :


Les règles de surcharge.

L’opérateur d’indexation.

Les opérateurs arithmétiques.

Les opérateurs relationnels.

Les opérateurs de
transtypage.

Les opérateurs de redirection.
La surcharge des opérateurs

Les règles de surcharge


Le principe

En C++ il est possible de surcharger la majorité des opérateurs de


base, afin de les utiliser avec des instances des classes définies
par le développeur :
L’opérateur d’indexation : []
Les opérateurs arithmétiques :
+, -, *, /, %, +=, -=, *=, /= et %=
Les opérateurs d’incrémentation et de décrémentation :
++ et –-
Les opérateurs de logique binaire :
^, &, |, ~, << (décalage à gauche), >> (décalage à droite), ^=, &=,
|=, <<= et >>=
Les opérateurs relationnels :
&&, ||, !, ==, !=, <, <=, >=, et >

La surcharge des opérateurs

Les règles de surcharge


Le principe

En C++ il est possible de surcharger la majorité des opérateurs de


base, afin de les utiliser avec des instances des classes définies
par le développeur :
L’opérateur d’affectation : = (déjà abordé)
L’opérateur de transtypage : ()
Les opérateurs d’allocation et de désallocation de la mémoire
: new, new[], delete et delete[]
Les opérateurs de redirection : >> et << (mêmes symboles
que les opérateurs de décalage mais sémantique totalement
différente).
L’opérateur ->
La surcharge des opérateurs

Les règles de surcharge


Les restrictions

Il existe cependant quelques restrictions :

Les operateurs ci-dessous ne peuvent pas être surchargés :

. (le point) : l’opérateur de sélection d’un membre d’une


classe ;
:: (deux points – deux points) : l’opérateur de résolution de
portée ;
?: (point d’interrogation – deux points) : l’ opérateur
d’expression conditionnelle ;
sizeof : l’opérateur « taille de ».

La surcharge des opérateurs

Les règles de surcharge


Les restrictions

Il existe cependant quelques restrictions :

on ne peut pas changer l’arité d’un opérateur (nombre


d’opérandes) ;
on ne peut pas changer la priorité des opérateurs ;
on ne peut pas changer l’associativité des opérateurs ;
on ne doit pas changer la signification d’un opérateur (on
pourrait rendre le code incompréhensible) ;
on ne peut pas inventer de nouveaux opérateurs ;
l’opérateur doit porter sur au moins un objet d’une classe.
La surcharge des opérateurs

Les règles de surcharge


Les deux types de surcharge

Un opérateur peut être surchargé de deux manières :

La surcharge interne :
L’opérateur est une méthode de la classe.

La surcharge externe :
L’opérateur est une fonction amie de la classe.

Surcharge interne: le premier operande est toujours l’objet auquel


cette fonction s’applique donc cette méthode de surcharge est
bien adaptée (mais la surcharge externe serait possible) pour les
opérateurs modifiant l’objet sur lequel ils travaillent.

La surcharge des opérateurs

Les règles de surcharge


L’utilisation d’un opérateur surchargé

Lorsque cet opérateur est surchargé, nous pouvons l’utiliser de la


manière suivante (on suppose l’existence des objets A, B et C,
trois instances de la classe Cl) :

Cas de la surcharge interne :


C = A.operator+(B);

Cas de la surcharge externe :


C = operator+ (A,B);

Dans les deux cas de figure, ces écritures sont équivalentes à


l’écriture “courante” C = A + B;
La surcharge des opérateurs

Les règles de surcharge


La classe Vecteur

Nous allons définir une classe Vecteur qui contiendra 3 attributs


de types double pour stocker les 3 coordonnées

Cette classe contiendra :


Un constructeur prenant 3 paramètres ;
Une méthode d’affichage ;
Un opérateur + pour faire la somme de deux vecteurs ;
Un opérateur * pour calculer le produit vectoriel de deux
vecteurs ;
Une fonction amie * pour calculer le produit d’un vecteur par
un scalaire.

La surcharge des opérateurs

Les règles de surcharge


La classe Vecteur – La déclaration de la classe

Nous écrivons d’abord la déclaration de cette nouvelle classe


avec la signature des opérateurs à surcharger.

class Vect
{
double x, y, z;
public :
Vect (double _x=0,
double _y=0,
double _z=0) : x(_x), y(_y), z(_z) {}
void afficher ();
Vect operator+ (const Vect&);
Vect operator* (const Vect&);
friend Vect operator* (const double&, const Vect&);
};
La surcharge des opérateurs

Les règles de surcharge


La classe Vecteur – La méthode afficher

Nous implantons ensuite la méthode afficher qui envoie


simplement la valeur des attributs x, y et z sur la sortie standard.

void Vect::afficher ()
{
cout << "(" << x << "," << y;
cout << "," << z << ")" << endl;
}

La surcharge des opérateurs

Les règles de surcharge


La classe Vecteur – L’opérateur +

Nous implantons l’opérateur + comme la somme de deux


vecteurs, nous construisons donc un vecteur dont les
composantes sont les sommes des composantes des vecteurs de
départ.

Vect Vect::operator+ (const Vect& arg)


{
Vect somme;

somme.x = x + arg.x;
somme.y = y + arg.y;
somme.z = z + arg.z;

return somme;
}
La surcharge des opérateurs

Les règles de surcharge


La classe Vecteur – Le premier opérateur *

Nous implantons l’opérateur * comme le produit vectoriel de deux


vecteurs, nous construisons donc un vecteur et nous calculons les
composantes de ce vecteur.

Vect Vect::operator* (const Vect& arg)


{
Vect produitVectoriel;

produitVectoriel.x = y * arg.z - z * arg.y;


produitVectoriel.y = - z * arg.x + x * arg.z;
produitVectoriel.z = x + arg.y + y * arg.x;

return produitVectoriel;
}

La surcharge des opérateurs

Les règles de surcharge


La classe Vecteur – Le deuxième opérateur *

Nous implantons une autre version de l’opérateur * qui consiste


simplement à multiplier un vecteur par un scalaire.

Vect operator* (const double& arg1, const Vect& arg2)


{
Vect produitParUnScalaire;

produitParUnScalaire.x = arg1 * arg2.x;


produitParUnScalaire.y = arg1 * arg2.y;
produitParUnScalaire.z = arg1 * arg2.z;

return produitParUnScalaire;
}
La surcharge des opérateurs

Les règles de surcharge


La classe Vecteur – Le main

Il reste à tester notre classe en utilisant le main ci-dessous :

int main(int argc, char *argv[]) {


Vecteur a (1,2,3);
Vecteur b (4,5,6);
double c = 10;
Vecteur d = a + b; /* methode operator+ */
Vecteur e = a * b; /* methode operator* */
Vecteur f = c * a; /* fonction operator* */
cout << "Le vecteur a="; a.afficher ();
cout << "Le vecteur b="; b.afficher ();
cout << "Le scalaire c=" << c << endl;
cout << "Le vecteur d=a+b="; d.afficher ();
cout << "Le vecteur e=a*b="; e.afficher ();
cout << "Le vecteur f=c*a="; f.afficher ();
return EXIT_SUCCESS; }

La surcharge des opérateurs

Les règles de surcharge


La classe Vecteur – L’exécution

Lorsque nous exécutons le programme, nous obtenons le résultat


suivant :
La surcharge des opérateurs

Les opérateurs arithmétiques


Rappel concernant les opérateurs arithmétiques

Les opérateurs arithmétiques permettent d’effectuer des


opérations mathématiques usuelles entre deux membres.

Les opérateurs de comparaison sont les suivantes :


+, -, *, /, %, +=, -=, *=, /= et %=

Ces opérateurs sont déjà implantés pour les types simples mais
on peut naturellement les surcharger pour effectuer des
opérations entre des instances d’une même classe par exemple.

La surcharge des opérateurs

Les opérateurs arithmétiques


Exemple de surcharge des opérateurs += et *=

Nous avons déjà traité en introduction la surcharge des opérateur


+ et *.

Nous allons donc nous intéresser à l’implantation de leur


« variante » += et *=.
La surcharge des opérateurs

Les opérateurs arithmétiques


Exemple de surcharge de += et *= – Déclaration

Nous commençons d’abord par écrire la signature de ces


opérateurs dans la déclaration de la classe Vect.

class Vect
{
/* les autres membres de la classes */

Vect& operator+= (const Vect&);


Vect& operator*= (const Vect&);
friend Vect& operator*= (Vect&, const double&);
};

La surcharge des opérateurs

Les opérateurs arithmétiques


Exemple de surcharge de += et *= – Implantation

Nous implantons l’opérateur += comme le cumul de deux vecteurs


Nous cumulons les composantes des vecteurs de départ et nous
retournons le vecteur sur lequel nous avons invoqué l’opérateur
(et qui a donc été modifié).

Vect& Vect::operator+= (const Vect& arg)


{
x += arg.x;
y += arg.y;
z += arg.z;

return *this;
}
La surcharge des opérateurs

Les opérateurs arithmétiques


Exemple de surcharge de += et *= – Implantation

Nous implantons l’opérateur *= comme le produit vectoriel de


deux vecteurs
Nous construisons un vecteur temporaire pour calculer les
composantes du produit vectoriel puis nous écrasons le vecteur
sur lequel a été invoqué la méthode.

Vect& Vect::operator*= (const Vect& arg)


{
*this = *this * arg;
return *this;
}

La surcharge des opérateurs

Les opérateurs arithmétiques


Exemple de surcharge de += et *= – Implantation

Nous implantons une autre version de opérateur *= qui consiste


simplement à multiplier un vecteur par un scalaire.
On procède de la même manière que précédemment.

Vect& operator*= (Vect& arg1, const double& arg2)


{
arg1 = arg2 * arg1;

return arg1;
}
La surcharge des opérateurs

Les opérateurs arithmétiques


Exemple de surcharge de += et *= – Test

Il reste à tester notre classe en utilisant le main ci-dessous :

int main (int argc, char** argv)


{

a+=b;
b*=b;
d*=c;
cout << "Le nouveau vecteur a=";
a.afficher ();
cout << "Le nouveau vecteur b=";
b.afficher ();
cout << "Le nouveau vecteur d=";
d.afficher ();
return EXIT_SUCCESS;
}

La surcharge des opérateurs

Les opérateurs arithmétiques


Exemple de surcharge de += et *= – test

Lorsque nous exécutons le programme, nous obtenons le résultat


suivant :
La surcharge des opérateurs

Les opérateurs relationnels


Rappel concernant les opérateurs relationnels

Les opérateurs relationnels permettent d’effectuer des


comparaisons entre deux membres.

Ils retournent un booléen (le type de retour est bool) indiquant si


la comparaison est vérifiée ou non.

Les opérateurs de comparaison sont les suivantes :


== , != , < , <= , > , >= , && et ||

Ces opérateurs sont déjà implantés pour les types simples mais
on peut naturellement les surcharger pour effectuer des
comparaisons entre des instances d’une même classe par
exemple.

La surcharge des opérateurs

Les opérateurs relationnels


Exemple de surcharge de ces opérateurs – Déclaration

Nous allons effectuer une surcharge externes de ces opérateurs


pour pourvoir comparer entre deux instances de la classe Vect.
Pour cela, nous allons les déclarer comme des fonctions amies de
la classe Vect (nous ajoutons aussi une méthode getNorme() qui
nous servira plus loin.

class Vect
{
/* les autres membres de la classes */
double getNorme ();
friend bool operator== (Vect, Vect);
friend bool operator!= (Vect, Vect);
friend bool operator< (Vect, Vect);
friend bool operator<= (Vect, Vect);
friend bool operator> (Vect, Vect);
friend bool operator>= (Vect, Vect);
};
La surcharge des opérateurs

Les opérateurs relationnels


Exemple de surcharge de l’operateur ==

L’opérateur == permet de vérifier si deux instances de Vect sont


égales en vérifiant si toutes leurs composantes sont égales deux
à deux.

bool operator== (Vect v, Vect w)


{
return (v.x==w.x) && (v.y==w.y) && (v.z==w.z) ;
}

La surcharge des opérateurs

Les opérateurs relationnels


Exemple de surcharge de l’opérateur !=

L’opérateur != permet de vérifier si deux instances de Vect sont


différentes en vérifiant si au moins l’une de leurs composantes
sont différentes deux à deux.

bool operator!= (Vect v, Vect w)


{
return (v.x!=w.x) || (v.y!=w.y) || (v.z!=w.z) ;
}
La surcharge des opérateurs

Les opérateurs relationnels


Exemple de surcharge des opérateurs ==, !=, <, <=, > et >=

Les opérateurs >, >=, < et <= permettent de comparer la norme de


deux instances de Vect.
Pour cela, nous devons implanter la méthode getNorme() qui va
calculer la racine carrée de la somme des carrés des
composantes (ce qui nécessite d’inclure le fichier math.h) et
retourner la valeur finale sous forme d’un double.

double Vect::getNorme ()
{
double normeAuCarre = pow (x, 2);
normeAuCarre += pow (y, 2);
normeAuCarre += pow (z, 2);

return sqrt (normeAuCarre) ;


}

La surcharge des opérateurs

Les opérateurs relationnels


Exemple de surcharge des opérateurs ==, !=, <, <=, > et >=

Maintenant que nous disposons de la méthode getNorme(),


l’implantation de ces opérateurs s’en trouve facilitée.

bool operator< (Vecteur v, Vecteur w)


{
return (v.getNorme () < w.getNorme ());
}

bool operator<= (Vecteur v, Vecteur w)


{
return (v.getNorme () <= w.getNorme ());
}
La surcharge des opérateurs

Les opérateurs relationnels


Exemple de surcharge des opérateurs ==, !=, <, <=, > et >=

Maintenant que nous disposons de la méthode getNorme(),


l’implantation de ces opérateurs s’en trouve facilitée.

bool operator> (Vecteur v, Vecteur w)


{
return (v.getNorme () > w.getNorme ());
}

bool operator>= (Vecteur v, Vecteur w)


{
return (v.getNorme () >= w.getNorme ());
}

La surcharge des opérateurs

Les opérateurs relationnels


Exemple de surcharge de ces opérateurs – Test

Comme précédemment, nous allons modifier le main afin de tester


ces nouveaux opérateurs.

int main (int argc, char** argv)


{

cout << " a == a ? -> " << (a==a) << endl;
cout << " a == b ? -> " << (a==b) << endl;
cout << " a != a ? -> " << (a!=a) << endl;
cout << " a != b ? -> " << (a!=b) << endl;
cout << " Norme de a = " << a.getNorme() << endl;
cout << " Norme de b = " << b.getNorme() << endl;
cout << " Na < Nb -> " << (a<b) << endl;
cout << " Na <= Nb -> " << (a<=b) << endl;
cout << " Na > Nb -> " << (a>b) << endl;
cout << " Na >= Nb -> " << (a>=b) << endl;
return EXIT_SUCCESS;
}
La surcharge des opérateurs

Les opérateurs relationnels


Exemple de surcharge de ces opérateurs – Tests

Si nous exécutions ce programme, nous obtenons alors l’affichage


suivant :

La surcharge des opérateurs

Les opérateurs de redirection


Rappel concernant les opérateurs de redirections

En langage C++, les entrées sorties peuvent être manipulées au


travers de flux :
cin correspond au flux d’entrée standard ;
cout encapsule la sortie standard ;
cerr permet d’accéder à la sortie standard d’erreur.

C++ fournit les opérateurs de redirection << et >> pour écrire vers
les flux de sortie ou lire depuis les flux d’entrée des variables de
type simple comme char, int , double …

int a;
cin >> a;
double b = 20;
cout << "La valeur de b est " << b << endl;
La surcharge des opérateurs

Les opérateurs de redirection


Les règles de surcharge de ces opérateurs

Il est possible de surcharger ces opérateurs de manière à pouvoir


écrire ou lire des données de type utilisateur.

Ces opérateurs doivent toujours garder la même signature :


pour une surcharge interne :

ostream& operator<<(ostream&);
istream& operator>>(istream&);

pour une surcharge externe :

friend ostream& operator<<(ostream&, const Classe&);


friend istream& operator>>(istream&, const Classe&);

La surcharge des opérateurs

Les opérateurs de redirection


La surcharge interne de l’opérateur << – Déclaration

Nous allons surcharger l’opérateur << afin de l’utiliser à la place


de la méthode affichage.

Pour cela, nous commençons dans le déclarer dans la classe


Vect.

class Vect
{
/* les autres membres de la classes */
ostream& operator<< (ostream&);
};
La surcharge des opérateurs

Les opérateurs de redirection


La surcharge interne de l’opérateur << – Implantation

Nous implantons ensuite la méthode en deux étapes :


on écrit dans le flux de sortie passé en paramètre de la
méthode (au lieu d’écrire directement dans cout)
on retourne ce flux.
ostream& Vect::operator<< (ostream& flux)
{
flux << "(" << x << "," << y;
flux << "," << z << ")";
return flux;
}
Cette méthode semble faire double emploi avec la méthode
afficher mais elle est plus souple d’utilisation car nous pouvons
par exemple l’utiliser pour écrire dans un fichier.

La surcharge des opérateurs

Les opérateurs de redirection


La surcharge interne de l’opérateur << – Test

Comme précédemment, nous allons modifier le main afin de tester


ce nouvel opérateur.

int main (int argc, char** argv)


{

cout << "Avec la méthode afficher ()" << endl;
cout << "Vecteur a : "; a.afficher ();

cout << "Avec l'operateur <<" << endl;


cout << "Vecteur a : ";

a << cout << endl; /* On doit inverser a et cout !!


*/

return EXIT_SUCCESS;
}
La surcharge des opérateurs

Les opérateurs de redirection


La surcharge interne de l’opérateur << – Tests

Si nous exécutions ce programme, nous obtenons alors l’affichage


suivant :

La surcharge des opérateurs

Les opérateurs de redirection


La surcharge externe de l’opérateur << – Déclaration

Nous allons surcharger l’opérateur << cette fois pour la classe


Droite afin de montrer la différence entre les deux manière de
procéder

Pour cela, nous commençons dans le déclarer en tant que


fonction amie dans la classe Droite.

class Droite
{
/* les autres membres de la classes */
friend ostream& operator<< (ostream&, Droite&);
};
La surcharge des opérateurs

Les opérateurs de redirection


La surcharge externe de l’opérateur << – Implantation

Nous implantons toujours la méthode en deux étapes :


on écrit dans le flux de sortie passé en paramètre de la
méthode (au lieu d’écrire directement dans cout)
on retourne ce flux.

ostream& operator<< (ostream& flux, Droite& d)


{
flux << "ox=" << d.getOx ();
flux << " oy=" << d.getOy ();
flux << " oz=" << d.getOz ();
flux << " vx=" << d.getVx ();
flux << " vy=" << d.getVy ();
flux << " vz=" << d.getVz ();

return flux;
}

La surcharge des opérateurs

Les opérateurs de redirection


La surcharge externe de l’opérateur << – Test

Il ne reste plus qu’à modifier le main …

int main (int argc, char** argv)


{

cout << "Avec la méthode afficher ()" << endl;
cout << "Droite g : "; g.afficher ();

cout << "Avec l'operateur <<" << endl;


cout << "Droite g : " << g << end;

return EXIT_SUCCESS;
… pour constater que l’inversion n’est plus nécessaire.
}
Les templates
Les templates
(ou « patrons » en fançais)
Points abordés :


Le principe.

La déclaration des
templates

Les templates de
fonctions.

Les templates de
classes.

Instanciation des
templates.

Exercice.

Les templates

Le principe
La factorisation des traitements semblables

Dans les modules précédents, nous avons vu que les


mécanismes d’héritage et de polymorphisme permettent de
réduire la taille du code en factorisant des éléments comme les
attributs et les méthodes.

Le C++ propose un autre mécanisme qui permet de factoriser des


éléments de code et qui se révèle dans certains cas plus efficace
que l’héritage et le polymorphisme.

Ce mécanisme est appelé la méta-programmation.


Les templates

Le principe
La méta-programmation

Cette méta-programmation repose sur l’utilisation de patrons,


encore appelés templates en anglais.

Il s’agit de types génériques ou des constantes qui permettent de


paramétrer la définition des fonctions et des classes.

La méta-programmation consiste donc à implanter une fonction


ou une classe qui manipule un type générique ou une constante
de sorte que le compilateur soit capable de construire les
fonctions ou les classes avec les vrais types ou les vraies valeurs
lors de l’instanciation des templates.

Les templates

La déclaration des paramètres templates


Les paramètres template simples

Pour déclarer l’existence d’un paramètre template, il suffit de


respecter la syntaxe ci-dessous :

template <class nom1[=type], typename nom2[=type], …


>

Mot clé pour indiquer que


nom1 est une classe

Pour spécifier un type par


défaut si on n’indique rien
Mot clé pour indiquer que
nom2 est type comme char,
int, double …
Les templates

La déclaration des paramètres templates


Les types par défaut et les contraintes sur les déclarations

On peut déclarer par exemple le template ci-dessous, qui


contient un type par défaut pour V.

template <class U, typename V=int, class


W>
Une déclaration avec ou sans type par défaut pose une
contrainte sur les déclaration des templates suivants, par
conséquent le template ci-dessous sera refusé par le compilateur

template <class U=int, typename V=int, class W>

Les templates

La déclaration des paramètres templates


Les paramètres template imbriqués

Le C++ permet d’utiliser une classe template comme type


générique. Il faut alors la déclarer comme étant une template à
l’intérieur même de la déclaration template.

template <template <class T> class C [,…]>

C est un classe générique qui utilise la classe générique T.

Ce type de paramètre template est appelé un paramètre


template template.
Les templates

La déclaration des paramètres templates


Les constantes template

La constante template est un cas particulier de paramètres


template. Sa déclaration s’effectue en respectant la syntaxe ci-
dessous :

template <type paramètre[=valeur] [,…]>

A l’instar des type générique, la constante template permet de


paramétrer la définition des fonctions et de classes avec des
variables de type entier qui peuvent avoir des valeurs par défaut.

Les templates

La déclaration des paramètres templates


Les constantes template

Le type des constantes template doit être l’un des types


suivants :
un type intégral (char, int, long, short et leurs versions non
signées) ;
un type énuméré (enum)
un pointeur ou une référence d’objet ;
un pointeur ou une référence de fonction ;
un pointeur sur membre.
Les templates

Les fonctions templates


La déclaration des fonctions template

Après avoir traité la déclaration des paramètres template, nous


allons voir leur utilisation, d’abord au travers des fonctions
template.

De telles fonctions se déclare de la même manière que des


fonctions normales à la différence qu’elle sont précédées de la
déclaration des paramètres template.

template <class nom1[=type], typename nom2[=type], …


>
typeDeRetour fonction (nom1 arg1, nom2 arg2 …);

Les templates

Les fonctions templates


La déclaration des fonctions template

Les types du paramètres template doivent être utilisés dans la


liste des paramètres de la fonction afin de permettre au
compilateur de réaliser l’identification des types génériques avec
les types à utiliser lors de l’instanciation de la fonction.

Les arguments de type « paramètres template » peuvent être


utilisés à l’intérieur du code de la fonction comme les type
« normaux ».
Les templates

Les fonctions templates


Exemple

On définit une fonction min qui renvoie le minimum entre deux


instances d’un objet de type T. La classe devra donc comporter
l’opérateur < pour pouvoir être utilisée en paramètre de cette
fonction.

template <class T>


T min(T x, T y)
{
if (x<y)
return x;
else return y;
}

Les templates

Les classes templates


La déclaration des classes template

Les paramètres template peuvent également être utilisés dans le


codage des classes (mais également des structures et des
unions).

La déclaration de ce type de classes, s’effectue simplement en


faisant précéder la déclaration de la classe de celle du paramètre
template.

template <class nom1[=type], typename nom2[=type], …


>
class C;
Les templates

Les classes templates


La déclaration des fonctions template

A la différence des fonctions templates, les méthodes d’une


classe template ne doivent pas nécessairement utiliser les types
du paramètres template dans leur liste d’argument.

Les arguments de type « paramètres template » peuvent être


utilisés à l’intérieur du code de la classes comme les type
« normaux » au niveau des attributs et dans le code des
méthodes.

Les templates

Les classes templates


Exemple

On définit une classe générique Point munie de deux attributs


(l’abscisse et l’ordonnée) et de l’opérateur [].

template <typename T=int>


class Point
{
private :
T x;
T y;

T& operator[] (int n)


{
if (n == 0)
return x;
else return y;
}
}
Les templates

Les classes templates


La déclaration des méthodes template

Il est possible d’ajouter des méthodes templates à l’intérieur des


classes, qu’elles soient template ou non. La déclaration de telle
s’effectue de la même manière que celle des fonctions template.

On peut, par exemple, ajouter la méthode template ci-dessous à


la classe précédente (la classe générique V doit disposer d’un
opérateur [ ] semblable à celui de Point ).

template <class V>


T& operator+= (V vecteur)
{
x += vecteur[0];
y += vecteur[1];
return *this;
}

Les templates

Les classes templates


Les restrictions concernant les méthodes template

Les méthodes virtuelles ne peuvent pas être des méthodes


template.

Si une méthode template a le même nom qu’une méthode


virtuelle d’une classe de base, elle n’est pas considérée comme
une redéfinition de méthode. (le mécanisme de virtualité ne
fonctionne pas).
Les templates

L’instanciation des templates


Le principe

La déclaration des fonctions templates, des classes template et


des méthodes templates ne génère aucun code tant que tous
les paramètres template n’ont pas pris chacun une valeur
spécifique.

Lorsqu’une fonction template, une classe template ou une


méthode template sont utilisées, il faut fournir des valeurs et/ou
des types aux paramètres template pour permettre au
compilateur de générer le code.

Cette opération est appelé l’instanciation des template et elle


peut être implicite ou explicite.

Les templates

L’instanciation des templates


L’instanciation implicite

L’instanciation implicite est effectuée par le compilateur lorsque


celui-ci rencontre un appel de fonction template, une
instanciation d’une classe template ou l’invocation d’une méthode
template.

Le compilateur se base sur le contexte pour renseigner les


différents paramètres template et ainsi générer le code
Les templates

L’instanciation des templates


L’instanciation implicite – Exemple

Dans l’exemple ci-dessous, le compilateur peut instancier la


fonction template min en remplaçant T par int.

int i = min (1,2);

Dans l’exemple ci-dessous, le compilateur n’est pas capable


d’instancier la fonction car il y a une ambigüité entre le type int et
le type float.

int i = min (1,2.0);

Les templates

L’instanciation des templates


L’instanciation explicite

L’instanciation explicite est réalisée par le programmeur en


spécifiant le type ou la valeur pour chaque paramètre.

Pour cela, il doit utiliser une syntaxe proche de celle de la


déclaration en utilisant le mot clé « template », suivi d’un nom
suivi des arguments placés entre « < > »

template nom <arg1, ar2, …>


Les templates

L’instanciation des templates


L’instanciation explicite – Les raccourcis d’écriture

Dans certains cas, il peut exister des raccourcis d’écritures :


Pour la fonction template min, on peut écrire :

template int min (int, int);

Pour la classe générique Point, on peut écrire l’instruction


pour utiliser le type par défaut, int :

template Point <>;

La gestion des erreurs


Points abordés :


Le principe.

La levée d’une
exception.

Le bloc try …
catch.
La gestion des erreurs

Le principe
La gestion des erreurs en langage C

En langage C, la plupart du temps, les fonctions retournent des


informations particulières lorsqu’une erreur survient.

Le programmeurs doit alors tester si la sortie de la fonction est


une sortie valide ou non afin d’activer le traitement approprié.

Par exemple, la fonction malloc retourne NULL si elle n’a pas pu


réserver la quantité d’octets désirée. Le programmeur doit donc
tester si la sortie de cette fonction est à NULL ou non.

La gestion des erreurs

Le principe
Le principe de gestion des erreurs en langage C++

Le langage C++ propose un mécanisme plus évolué qui permet de


faire une séparation plus nette entre le fonctionnement normal et
le fonctionnement exceptionnel relevant du traitement d’une
erreur. Le principe est le suivant :
on essaye d’exécuter une fonction ;
si la fonction rencontre un problème, elle lance un objet
appelée exception et elle s’arrête ;
L’objet lancé doit alors être attrapé par la fonction appelante
afin de traiter l’erreur, si ce n’est pas le cas, la fonction
appelante relance de nouveau l’exception et s’arrête … on
remonte ainsi jusqu’à la fonction main qui est arrêté si elle
n’attrape pas l’objet pour traiter l’erreur.
La gestion des erreurs

La levée d’une exception


Le mot clé throw

Lorsqu’une méthode ou une fonction rencontre un problème, elle


lance (on dit aussi « elle lève ») une exception en utilisant le mot
clé throw.
Ce mot clé lance un objet avec l’information fournie en paramètre
de throw.
throw 4; // Lance une exception de type int avec la
// valeur 4

throw "erreur"; // Lance une exception de type char*

// avec la chaîne « erreur »

throw E (4); // Lance une exception de type classe E

// avec l’argument 4 pour le


constructeur

La gestion des erreurs

La levée d’une exception


La spécification des exceptions levées par une fonction

Par défaut, un fonction est réputée lever tout type d’exception. Il


peut être parfois intéressant de restreindre cette possibilité en
indiquant dans la signature de la fonction ou de la méthode la liste
des exceptions pouvant être lancées.

Pour cela, il suffit d’écrire, après la signature de la fonction, le mot


clé throw suivi de la liste des types entre parenthèses

int fct (char, double) throw


(int,char,E);
La gestion des erreurs

Le bloc try … catch


La tentative d’exécution et le traitement de l’exception

La fonction susceptible de lever une exception doit être appelée


dans un bloc try … catch :
La section try contient entre accolades la ou les instructions
susceptible de lever des exceptions
La (ou les) section(s) catch contient (contiennent) les
instructions destinée à traiter l’exception.

try
{
// Instructions susceptibles de lever des
exceptions
}
catch (int e)
{
// Traitement de l’exception de type int
}

La gestion des erreurs

Le bloc try … catch


La section catch

Une section catch se définit grâce au mot clé catch, suivi d’un
argument placé entre parenthèses et correspondant au type
d’objet lancé.
Cet argument est une variable locale à la section catch et il peut
être utilisé par les instructions de cette section.

try
{
// Instructions susceptibles de lever des
exceptions
}
catch (int e)
{
cout << "on a lancé l’erreur numéro" << e << endl;
}
La gestion des erreurs

Le bloc try … catch


La recherche de la section catch appropriée

Le langage C++ permet de placer plusieurs sections catch, les


une à la suite des autres.
Le programme teste alors les sections, les unes après les autres
et elle s’arrête à la première section catch correspondante.

try
{}
catch (char c)
{}
catch (int i)
{}
catch (E objE)
{}

La gestion des erreurs

Le bloc try … catch


Le gestionnaire d’exception universel

Si on ne connaît pas exactement la nature de l’exception


susceptible dans la section try, nous pouvons utiliser un
gestionnaire d’exception universel.
Pour cela, il suffit simplement de placer « … » dans les
parenthèse.
try
{}
catch (...)
{}
Comme ce gestionnaire traite toutes les exceptions, il doit être
placé en dernier (sinon les gestionnaires spécialisés ne seront
jamais invoqués).
Séance 8

Séance 8

EXERCICES
(sous code::blocks)

76
INFO 9-10
EXERCICE 1 Séance 8

Fonction template simple


Réaliser une fonction template permettant d’afficher à l’écran la
valeur du paramètre passé.

Testez dans votre main pour un short, un float, un double, un


caractère et une chaîne de caractère

77
INFO 9-10

Séance 8
EXERCICE 2
Fonction template avec une classe utilisateur
+ surcharge externes d’opérateurs
Réaliser une classe Voiture et une fonction template max
permettant d’obtenir le comportement suivant :
int main()
{
Voiture v1 ("Dacia","Sandero", "7000");
Voiture v2 ("Lamborghini","Veneno",
"3530000");
cout << "Voiture la plus chere :"
<<max(v1,v2)<< endl;
Voiture v3 ("Citroen","Deudeuche", "1000");
v2=v3;
cout << "Voiture la plus chere :"
<<max(v1,v2)<< endl;

return 0;
}

78
INFO 9-10
EXERCICE 3 Séance 8

Classe template
Réaliser une classe template nommée Tableau :
• Utilisant le type paramétré T pour définir un attribut de type
tableau (10 éléments) et pour déclarer 2 fonctions membres
ajouter et renvoyer.
•Une autre méthode NombreElement permettra d’avoir le nombre
élément.
Test :

79
INFO 9-10

EXERCICE 3 Séance 8

Classe template
Modifier votre code pour augmenter la liste d argument du
template en mettant par défaut le nombre d’élément souhaité à
n=10.

80
INFO 9-10
EXERCICE 4 Séance 8

Exception
Modifier le code de l’exercice 5 du premier cours (deboggage de
l’erreur de division) pour que se lance une exception de type string
"Division par zero !" et qu’elle s’affiche dans le catch

81
INFO 9-10

Vous aimerez peut-être aussi