Cours 2023
Cours 2023
Cours 2023
J. Allali
Prog. C++
ENSEIRB-MATMECA
1
Plan
Introduction
2
Historique (wikipedia)
Normes:
• 1998: Normalisation du C++ par l’ISO (International Organization for
Standardization) et ANSI ISO/CEI 14882:1998
• 2003: ISO/CEI 14882:2003
• 2011: ISO/CEI 14882:2011
• 2014: ISO/CEI 14882:2014
• 2017: ISO/IEC 14882:2017
• 2020: ISO/IEC 14882:2020
3
C (++)
5
Trame du cours
Allocation
automatique & dynamique
variables & instances
destructeur
héritage
constructeur
this
attributs
visibilité polymorphisme?
définition syntaxique
virtual
metaprog templates
6
Plan
Allocation
7
Allocation automatique
Exemple
int i;
Ainsi, &i correspond à une adresse dans la pile à laquelle sizeof (int) octets sont
réservés.
8
Allocation dynamique
Dans le premier cas, on réserve sizeof(int) octets dans le tas, l’opérateur new
retourne l’adresse de début de cette zone.
Dans le deuxième cas, on réserve sizeof(int)*10 octets dans le tas, l’opérateur
new retourne l’adresse de début de cette zone.
Ces zones mémoires sont réservées tant que l’on n’a pas indiqué explicitement
que l’on souhaitait les libérer avec l’opérateur delete.
9
Allocation dynamique: libération
exemple
delete (new int); ou int *i=new int; delete i;
delete [] (new int [10]); ou int *p=new int[10]; delete [] p;
Une fois l’opérateur delete appelé, la mémoire qui était réservée à cette adresse
ne l’est plus.
10
Allocation: exemple complet
exemple complet
i n t *i = new i n t ;
i n t *t = new i n t [10];
delete i;
delete [] t;
12
Allocation: exemple complet
12
Allocation: exemple complet
12
Typage
Comme le C, le C++ dispose des types primitifs de base: int, float, double,
char, wchar_t, bool et void. A chacun de ces types est associée une taille en
octets.
À cela s’ajoute les pointeurs: type *. Une caractéristique importante est que,
quelque que soit type, un pointeur fait toujours la même taille (4 octets en 32
bits et 8 octets en 64 bits).
Toute variable doit être typée. Le type indiquera le nombre d’octets réservés
pour cette variable (dans la pile ou le segment de données) et l’interprétation
associée à ces octets.
13
Le mot clé auto
14
Le mot clé auto
fonction
i n t f();
a u t o i=f(); / / i e s t de t y p e i n t
a u t o t = new i n t [10]; / / t e s t de t y p e i n t *
tableau
a u t o s=" h e l l o "; / / s e s t de t y p e c o n s t c h a r *
a u t o c=s[0]; / / c e s t de t y p e c h a r e t non c o n s t c h a r
15
initialisation de variable = et {}
exemple
i n t i=0;
i n t j=12.2; / / j v a u t 12
char c= ’ d ’; / / c v a u t 100
char d=35.0; / / d v a u t 35
char e=35.8; / / e v a u t 35
f l o a t f=2.5; / / f v a u t 2 . 5
16
initialisation de variable = et {}
exemple
i n t i{0};
i n t j{12.2}; / / ERREUR
char c{ ’ d ’}; / / c v a u t 100
char d{35.0}; / / ERREUR
f l o a t f{2.5}; / / f v a u t 2 . 5
d o u b l e x{3.0};
f l o a t y{x}; / / WARNING
17
initialisation de variable = et {}
18
Plan
Classes
19
Plan
Classes
Définition et déclaration
Visibilité, friend, struct
Attributs et méthodes
this
espace de nom
constructeur
destructeur
static
exemple
exemple point
20
exemple matrix
Les classes: déclaration
21
Les classes: instanciation
ou automatique:
allocation automatique
A z;
A t{};
Dans les deux cas, une ressource (adresse mémoire) est associée à l’instance.
Dans l’allocation dynamique, cette ressource est située dans le tas, dans le cas
automatique elle est située dans la pile.
22
Plan
Classes
Définition et déclaration
Visibilité, friend, struct
Attributs et méthodes
this
espace de nom
constructeur
destructeur
static
exemple
exemple point
23
exemple matrix
Visibilité: définition
24
Visibilité: déclaration
La visibilité par défaut dans une classe est private. On modifie la visibilité de
la façon suivante
Source
c l a s s NomClasse {
attributs et méthodes privés
public:
attributs et méthodes publiques
protected:
attributs et méthodes protégés
...
};
On peut à tout moment changer la visibilité, celle-ci sera appliquée à toutes les
déclaractions suivantes jusqu’au prochain changement de visibilité.
25
Les structures: déclaration
En C++, les structures sont des classes dont la visibilité par défaut est public.
on voit que les structures C sont alors un cas particulier des structures C++
26
Les structures: instanciation
allocation automatique:
s t r u c t A { ... };
A x;
s t r u c t A x;
27
Friend: définition
Dans une classe A, le mot clé friend permet de donner à une fonction ou une
autre classe les même droits qu’une méthode de A.
une fonction f amie d’une classe A pourra accéder aux attributs privés de A
ainsi qu’aux attributs protected d’une des classes parents de A
28
Friend: déclaration
Fonction amie
c l a s s NomClasse { ...
f r i e n d v o i d fonctionAmie( i n t ,char ,NomClasse) ;
};
la visibilité courante n’importe pas pour déclarer une classe ou fonction amie.
29
Plan
Classes
Définition et déclaration
Visibilité, friend, struct
Attributs et méthodes
this
espace de nom
constructeur
destructeur
static
exemple
exemple point
30
exemple matrix
Attributs et méthodes: définition
31
Exemple de classe: 1
Un seul fichier
c l a s s Test {
i n t i; / / a t t r i b u t p r i v é
public:
v o i d print(){ / * m é t h o d e p u b l i q u e * /
printf(" T e s t \ n");
}
};
Test t;
t.print();
32
Exemple de classe: 1
Un seul fichier
c l a s s Test {
i n t i; / / a t t r i b u t p r i v é
public:
v o i d print(){ / * m é t h o d e p u b l i q u e * /
printf(" T e s t \ n");
}
};
32
Exemple de classe: 2
Deux fichiers
Test.hpp: Test.cpp:
c l a s s Test { # i n c l u d e " T e s t . hpp"
i n t i; / / a t t r i b u t p r i v é v o i d Test::print(){
public: printf(" T e s t \ n");
/ * méthode p u b l i q u e * / }
v o i d print(); Exemple.cpp:
}; Test t;
t.print();
33
Accès aux attributs
L’accès aux champs d’une classe se fait à l’aide de . ou -> selon que l’on
dispose d’une instance de classe ou d’un pointeur sur une instance de classe.
Cas automatique
ClasseA x;
x.i = 1 ; / / i e s t un a t t r i b u t p u b l i c de C l a s s e A
x.ClasseA::i = 1 ; / / idem
x.methode(); / / a p p e l de m é t h o d e
x.ClasseA::methode(); / / idem
34
Accès aux attributs
L’accès aux champs d’une classe se fait à l’aide de . ou -> selon que l’on
dispose d’une instance de classe ou d’un pointeur sur une instance de classe.
Cas dynamique
ClasseA *y = new ClasseA();
y->i = 1 ; / / i e s t un a t t r i b u t p u b l i c de C l a s s e A
y->ClasseA::i = 1 ; / / idem
y->methode(); / / a p p e l de m é t h o d e
y->ClasseA::methode(); / / idem
35
Plan
Classes
Définition et déclaration
Visibilité, friend, struct
Attributs et méthodes
this
espace de nom
constructeur
destructeur
static
exemple
exemple point
36
exemple matrix
Le mot clé this
Le mot clé this permet, lors d’un appel de méthode, de faire référence à
l’instance source (site d’appel).
Le type de la variable this est NomDeClasse const *: c’est un pointeur. (Nous
verrons plus tard le sens de const.)
Exemple
c l a s s A{
i n t attributPrive;
public:
v o i d setAttribut( i n t valeur){
t h i s ->attributPrive = valeur;
}
};
37
Le mot clé this
Le mot clé this permet, lors d’un appel de méthode, de faire référence à
l’instance source (site d’appel).
Le type de la variable this est NomDeClasse const *: c’est un pointeur. (Nous
verrons plus tard le sens de const.)
Exemple
c l a s s A{
i n t attributPrive;
public:
v o i d setAttribut( i n t valeur) {
attributPrive = valeur ;
}
};
Lorsqu’il n’y a pas de variable locale de même nom que l’attribut, on peut
omettre this. 38
Opérateur de portée
39
Plan
Classes
Définition et déclaration
Visibilité, friend, struct
Attributs et méthodes
this
espace de nom
constructeur
destructeur
static
exemple
exemple point
40
exemple matrix
Les espaces de nom
41
Espace de noms
Lors de l’écriture du code des fonctions ou des méthodes des classes dans le
fichier .cpp on peut soit ré-ouvrir l’espace de nom, soit indiquer explicitement
le nom de l’objet avec l’opérateur de portée.
A.hpp A.cpp
namespace tec {
class A { #include “A.hpp”
public: namespace tec {
v o i d m(); void A::m(){
}; ...
} }
}
42
Espace de noms
Lors de l’écriture du code des fonctions ou des méthodes des classes dans le
fichier .cpp on peut soit ré-ouvrir l’espace de nom, soit indiquer explicitement
le nom de l’objet avec l’opérateur de portée.
A.hpp A.cpp
namespace tec {
class A { #include “A.hpp”
public: void tec::A::m(){
v o i d m(); ...
}; }
}
Il n’y pas pas de limite sur le nombre d’espaces de nom différents pouvant être définis dans un
fichier ni sur le niveau d’imbrication de ces espaces. Cependant la taille des noms de fonctions
est limitée et les espaces de nom font partis du nom de la fonction. D’une certaine manière, une
classe peut-être vue comme un espace de nom particulier. 42
Espace de noms: using
Le mot clé using permet de rendre accessible tout ou partie d’un espace de nom
dans une fonction, un espace de nom ou tout le code:
namespace spatial {
class Navette {};
}
namespace missions {
Navette n; //Erreur!
}
Navette n; //Erreur!
void starWars(){
Navette n; //Erreur!
}
43
Espace de noms: using
Le mot clé using permet de rendre accessible tout ou partie d’un espace de nom
dans une fonction, un espace de nom ou tout le code:
namespace spatial {
class Navette {};
}
namespace missions {
spatial::Navette n; //ok
}
spatial::Navette n; //ok
void starWars(){
spatial::Navette n; //ok!
}
43
Utilisation de l’opérateur de portée
Espace de noms: using
Le mot clé using permet de rendre accessible tout ou partie d’un espace de nom
dans une fonction, un espace de nom ou tout le code:
namespace spatial {
class Navette {};
}
using spatial::Navette;
namespace missions {
Navette n; //ok
}
Navette n; //ok
void starWars(){
Navette n; //ok!
}
43
Espace de noms: using
Le mot clé using permet de rendre accessible tout ou partie d’un espace de nom
dans une fonction, un espace de nom ou tout le code:
namespace spatial {
class Navette {};
}
namespace missions {
Navette n; //Erreur!
}
using spatial::Navette;
Navette n; //ok
void starWars(){
Navette n; //ok!
}
43
Espace de noms: using
Le mot clé using permet de rendre accessible tout ou partie d’un espace de nom
dans une fonction, un espace de nom ou tout le code:
namespace spatial {
class Navette {};
}
namespace missions {
using spatial::Navette;
Navette n; //ok
}
Navette n; //Erreur!
void starWars(){
Navette n; //Erreur!
}
43
Espace de noms: using
Le mot clé using permet de rendre accessible tout ou partie d’un espace de nom
dans une fonction, un espace de nom ou tout le code:
namespace spatial {
class Navette {};
}
namespace missions {
Navette n; //Erreur!
}
Navette n; //Erreur!
void starWars(){
using spatial::Navette;
Navette n; //ok
}
43
Espace de noms: using
Le mot clé using permet de rendre accessible tout ou partie d’un espace de nom
dans une fonction, un espace de nom ou tout le code:
namespace spatial {
class Navette {};
}
namespace missions {
Navette n; //Erreur!
}
Navette n; //Erreur!
void starWars(){
using namespace spatial;
Navette n; //ok
}
43
Using
Un constructeur est une méthode de classe qui est appelée après la réservation
des ressources nécessaires à la création d’une instance de la classe et qui a pour
objectif d’initialiser les attributs de cette instance.
En C++, le constructeur est une méthode sans valeur de retour de même nom
que la classe.
46
Constructeur: exemple 1 fichier
Exemple.cpp
c l a s s Exemple {
d o u b l e pop;
public:
Exemple() { / / C o n s t r u c t e u r p a r d é f a u t
pop=0;
}
Exemple( d o u b l e d){ / / C o n s t r u c t e u r
pop=d;
}
};
47
Constructeur: exemple 2 fichiers
Exemple.hpp Exemple.cpp
c l a s s Exemple{ # i n c l u d e "Exemple . hpp"
d o u b l e pop;
public: Exemple::Exemple(){
Exemple(); pop=0;
Exemple( d o u b l e ); }
}; Exemple::Exemple( d o u b l e d){
pop=d;
}
48
Constructeur: initialisation des attributs
class A{
int pop;
public:
A(int value):pop(value){
}
};
49
Constructeur: initialisation des attributs
class A{
int pop;
public:
A(int pop):pop(pop){
}
};
49
Constructeur: initialisation des attributs
class A{
int pop;
public:
A(int pop):pop(pop*2){
}
};
49
Constructeur: initialisation des attributs
intdecremente(int i){
return i-1;
}
class A{
int pop;
public:
A(int pop):pop(decremente(pop)*2){
}
};
class A{
int pop;
int m(){
return 1;
}
public:
A(int pop):pop((m()+pop)*2){
}
};
La norme C++11 autorise le placement de valeur par défaut pour les attributs
non statiques à la déclaration:
c l a s s A{
i n t v=10; / / ou v { 1 0 } ;
std::string s{" h e l l o "};
public:
A(){
printf("%d %s \ n",v,s.c_str());
/ / a f f i c h e : 10 h e l l o
}
};
50
Constructeur: initialisation des attributs
public:
A();
};
51
Constructeur: initialisation des attributs
public:
A();
};
A::A():a(0),c( ’ a ’){} / / ok
52
Constructeur: et instanciation
Parenthésée Affectation
A x; A y;
A x(1); A y=1;
A x( ’ c ’); A y= ’ c ’;
A x(); / / E r r e u r :
/ / pas d ’ i n s t a n c i a t i o n !
53
Constructeur: et instanciation
54
Constructeur: et instanciation
Affectation Parenthésée
i n t i=0; i n t i(0);
char c= ’ a ’; char c( ’ a ’);
char *p=NULL; char *p(NULL);
55
Constructeur: et instanciation
Source:
A(); / / I n s t a n c i a t i o n u t i l i s a n t l e
/ / c o n s t r u c t e u r par d é f a u t
A(1); / / Avec l e c o n s t r u c t e u r A ( i n t ) ;
A; / / E r r e u r ! s y n t a x e i n v a l i d e .
56
Constructeur: et instanciation
Source:
v o i d f(A a){...}
...
f(A(1)); / / 1 . A p p e l de f o n c t i o n
f(1); / / 2 . U t i l i s a t i o n de l ’ i n s t a n c i a t i o n i m p l i c i t e
...
throw A(); / / 3 . L e v é e d ’ e x c e p t i o n
57
Constructeur: et tableaux
Source:
A *t=new A[10]; / / d y n a m i q u e
A tableau2[10]; / / a u t o m a t i q u e
⇒ Une classe ne comportant pas de constructeur par défaut ne peut pas être
utilisée avec les tableaux.
58
Constructeur: et tableaux
Tableaux:
c l a s s A{
public:
A( i n t );
};
...
A t[10]; / / E r r e u r !
A *t[10]; / / T a b l e a u de p o i n t e u r s
f o r ( i n t i=0;i<10;i++)
t[i]=new A(3); / / u t i l i s a t i o n du c o n s t r u c t e u r
/ / a l l o c a t i o n dynamique
...
f o r ( i n t i=0;i<10;i++)
d e l e t e t[i];
59
Constructeur: conversion
60
Constructeur: conversion
c l a s s A{
public:
A( i n t );
};
c l a s s B{
public:
B(A );
};
v o i d f(B q);
f(1); / / Ne marche p a s !
f(A(1)); / / ok
61
Constructeur: explicit
62
Constructeur: par recopie
63
Constructeur: par recopie
L’idée est donc d’avoir un constructeur dans la classe A qui prend une instance
de type A en argument:
c l a s s A{
public:
A(A x);
};
Une référence peut être vue comme un “alias” sur une instance.
Après l’initialisation:
65
Références
Exemple:
element(t,5)=10; / / m o t i f i e t [ 5 ]
Règle: Sauf pour les types primitifs, on ne prendra plus les instances par
recopie mais par référence.
66
Références
Parmis les avantages des références, on pourra noter qu’une référence ne peut
pas être nulle:
67
Références
Pour les valeurs de retour, on ne peut retourner une référence que si l’objet
renvoyé existe en dehors de la fonction (comme pour les pointeurs):
68
Références
i n t j;
print(j);
Matrix n(10000);
print(n);
Le mot clé const sert à indiquer qu’une donnée ne peut être modifiée:
Illustration du problème:
c l a s s A{ v o i d f( c o n s t A &x){
i n t i; x.get();
public: x.set(0); / / !
i n t get(){ r e t u r n i;} }
v o i d set( i n t v){
t h i s ->i=v; A y;
} f(y);
};
La référence est constante (l’instance ne doit pas être modifiée) mais l’appel de
méthode modifie l’objet !
Une méthode de classe peut être typée const. Cela indique que dans cette
méthode le type de this est const NomClasse const *
c l a s s A{ v o i d A::p() {
i n t i; // this : A * const
i n t *d; }
public:
v o i d m() c o n s t ; v o i d A::m() c o n s t {
v o i d p() ; // this : const A * const
}; // this : A const * const
// t h i s −> i : c o n s t i n t
// t h i s −>d : i n t * c o n s t
}
72
const
Lorsque l’on dispose d’une variable constante sur une instance, seules les
méthodes indiquées comme constantes peuvent être appelées sur cette instance:
A x;
c o n s t A & y=x;
x.m(); // ok
x.p(); // ok
y.p(); // erreur !
y.m(); // ok
Une méthode const peut appelée sur tout type de variable tandis qu’une
méthode non const ne peut être appelée que sur une variable non constante.
Règle: lors de l’écriture d’une méthode, on ne doit pas se demander si la
méthode doit être const mais plutôt si on a besoin qu’elle ne soit pas const.
Règle: Les arguments de fonction (non primitifs) seront des références
73
constantes sauf si l’on veut modifier l’argument.
const: surcharge
c l a s s A{ A x;
public: const A &y=x;
v o i d m(){ A const *p=&x;
printf(" l e s i t e d ’ a p p e l x.m(); //?
n ’ e s t pas c o n s t a n t "); y.m(); //?
} p->m(); //?
v o i d m() c o n s t {
printf(" l e s i t e d ’ a p p e l
e s t c o n s t a n t ")
}
};
74
const: argument de fonction
c l a s s A{ v o i d h( c o n s t A &c){
i n t i; g(c); / / !
public: f(c);
i n t get() c o n s t { r e t u r n i; } c.get();
}; }
v o i d f(A a){ / / c o p i e A x;
printf("%d \ n",a.get()); c o n s t A &y=x;
} h(x);
g(x);
v o i d g(A &b){ g(y);
printf("%d \ n",b.get());
}
75
Constructeur: recopie (suite)
Le constructeur fournit sera de type 1, si tous les constructeurs par recopie des
attributs sont de type 1, sinon il sera de type 2.
76
Constructeur: recopie par le compilateur
c l a s s Comp{
i n t i;
std::string s;
Matrix m;
public:
Comp( c o n s t Comp&c):i(c.i),s(c.s),m(c.m){}
};
77
Constructeur: par recopie
Règle: Toute classe nécessitant une copie profonde (allocation dynamique des
attributs) devra avoir un constructeur par recopie.
78
Constructeur: par recopie
c l a s s A{ A x;
i n t *i; A y(x);
public:
A():i(new i n t (0)){}
~A(){ d e l e t e i; }
};
Ici, il y a une erreur car on libère deux fois le même espace mémoire.
79
Constructeur: par recopie
c l a s s A{ A x;
i n t *i; A y(x);
public:
A():i(new i n t (0)){}
~A(){ d e l e t e i; }
A( c o n s t A &a):i(new i n t (*(a.i))){}
};
80
Constructeur: protected et private
Une classe n’ayant que des constructeurs protected ne pourra être instanciée
que par
• Une sous-classe
• Une classe ou fonction amie
• Une classe ou fonction amie d’une sous-classe
• Une méthode statique
Une classe n’ayant que des constructeurs private ne pourra être instanciée que
par
81
Plan
Classes
Définition et déclaration
Visibilité, friend, struct
Attributs et méthodes
this
espace de nom
constructeur
destructeur
static
exemple
exemple point
82
exemple matrix
Destructeur: définition
v o i d f(){
c l a s s Nom{
Nom n;
public:
Nom *p=new Nom;
~Nom(){}
d e l e t e p; / / A p p e l de p−>~Nom ( )
};
} / / A p p e l de n . ~ Nom ( )
Règle: toute classe ayant allouée dynamiquement ces attributs devra écrire un
destructeur.
83
Si l’instance a été allouée dynamiquement alors l’appel au destructeur est
déclenché par l’opérateur delete.
84
Destructeur: exemple
c l a s s Tab{ i n t main(){
i n t *data; Tab t(10); / / a l l o c a t i o n
public: } // liberation
Tab( i n t s){
data= new i n t [s];
}
~Tab(){
d e l e t e [] data;
}
};
Il est ainsi possible de "simuler" un tableau dans la pile avec une désallocation
automatique.
85
Destructeur: auto
c l a s s AutoInt{ // Utilisation :
i n t *t; i n t main(){
public: AutoInt t(new i n t [100]);
AutoInt( i n t *t):t(t); t.get(0);
i n t get ( i n t i) c o n s t { t.set(0,0);
r e t u r n t[i]; } / / l i b é r a t i o n auto .
}
v o i d set( i n t i, i n t v){
t[i]=v;
}
~AutoInt(){
i f (t!=NULL)
d e l e t e [] t;
}
};
c l a s s AutoInt{ // Utilisation :
i n t *t;
public: v o i d f(AutoInt ai){
AutoInt( i n t *t):t(t); } / / l i b é r a t i o n auto .
i n t get( i n t i) c o n s t ;
v o i d set( i n t i, i n t v); AutoInt tab(){
~AutoInt(){ AutoInt x(new i n t [100]);
i f (t!=NULL) r e t u r n x;
d e l e t e [] t; }
} i n t main(){
AutoInt(AutoInt &p){ AutoInt q(new i n t [100]);
t=p.t; f(q);
p.t=NULL;
} AutoInt r(tab());
}; } / / l i b é r a t i o n auto .
Une variable statique est une variable dont la durée de vie est égale à celle du
programme et dont la portée peut être réduite à une fichier, une classe, une
fonction ou méthode selon l’endroit où elle est déclarée.
Fichier.hpp Fichier.cpp
# i n c l u d e " F i c h i e r . hpp"
v o i d f();
s t a t i c d o u b l e v;
c l a s s A{
s t a t i c i n t i; i n t A::i=0;
public:
s t a t i c v o i d m(); v o i d A::m(){
}; this; / / erreur !
}
v o i d f(){
s t a t i c i n t compteur=0;
}
89
Static: et classes
Dans le contexte des classes, une méthode publique statique est équivalente à
une fonction amie (sauf pour la portée).
On peut utiliser le mot clé static en jonction avec const afin de définir des
propriétés constantes de classes:
c l a s s Chaine {
public:
s t a t i c c o n s t char END= ’ \ 0 ’;
};
Les variables static et const sont les seules à pouvoir être initialisées lors de la
déclaration dans le fichier .hpp.
90
Static: et classes
Dans le contexte des classes, une méthode publique statique est équivalente à
une fonction amie (sauf pour la portée).
On peut utiliser le mot clé static en jonction avec const afin de définir des
propriétés constantes de classes:
c l a s s Chaine {
public:
s t a t i c c o n s t char END;
};
c o n s t char Chaine::END= ’ \ 0 ’;
91
Plan
Classes
Définition et déclaration
Visibilité, friend, struct
Attributs et méthodes
this
espace de nom
constructeur
destructeur
static
exemple
exemple point
92
exemple matrix
Une classe compteur
L’objectif est d’écrire une classe Compteur telle que plusieurs instances de cette
classe puissent partager un même compteur qui sera ici implanté par un entier.
93
Compteur: Exemple1
94
Compteur: Exemple1
94
Compteur: Exemple1
94
Compteur: Exemple1
94
Compteur: Exemple1
94
Compteur: Exemple1
94
Compteur: Exemple1
94
Compteur: Exemple1
94
Compteur: Exemple1
94
Compteur: Exemple1
94
Compteur: Exemple2
95
Compteur: Exemple2
95
Compteur: Exemple2
95
Compteur: Exemple2
95
Compteur: Exemple2
95
Compteur: Exemple2
95
La classe Compteur
• Compteur();
• Compteur(const Compteur \&);
• ~Compteur();
96
Problème:
i n t main(){
Compteur c;
Compteur d;
d=c; / / R e c o p i e de c d a n s d
}
97
Problème:
98
Problème:
98
Problème:
98
Problème:
98
Problème:
98
Plan
Classes
Définition et déclaration
Visibilité, friend, struct
Attributs et méthodes
this
espace de nom
constructeur
destructeur
static
exemple
exemple point
99
exemple matrix
Exemple d’une classe basic Point
Point.hpp Point.cpp
class Point { # i n c l u d e " P o i n t . hpp"
double x , y ;
public : Point : : Point ( ) : x (0) , y (0){
Point ( ) ; }
P o i n t ( double x , double y ) ; P o i n t : : P o i n t ( double x , double y ) : x ( x ) , y ( y ){
}; }
#include <Point.hpp>
i n t main(){
Point p(2,3);
Point q;
Point *t=new Point[20];
d e l e t e [] t;
}
100
Exemple d’une classe basic Point
Point.hpp Point.cpp
class Point { # i n c l u d e " P o i n t . hpp"
double x , y ;
public : Point : : Point ( ) : x (0) , y (0){
Point ( ) ; }
P o i n t ( double x , double y ) ; P o i n t : : P o i n t ( double x , double y ) : x ( x ) , y ( y ){
double g e t x ( ) const ; }
void s e t x ( double ) ; double P o i n t : : g e t x ( ) const { return x ; }
}; v o i d P o i n t : : s e t x ( d o u b l e d ) { x=d ; }
101
Exemple d’une classe basic Point
Point.hpp Point.cpp
class Point { # i n c l u d e " P o i n t . hpp"
double x , y ;
public : Point : : Point ( ) : x (0) , y (0){
Point ( ) ; }
P o i n t ( double x , double y ) ; P o i n t : : P o i n t ( double x , double y ) : x ( x ) , y ( y ){
v o i d add ( c o n s t P o i n t &p ) ; }
P o i n t sum ( c o n s t P o i n t &p ) c o n s t ; v o i d P o i n t : : add ( c o n s t P o i n t &p ) { x+=p . x ; y+=p . y ; }
}; P o i n t P o i n t : : sum ( c o n s t P o i n t &p ) c o n s t {
P o i n t fsum ( c o n s t P o i n t &a , c o n s t P o i n t &b ) ; r e t u r n P o i n t ( x+p . x , y+p . y ) ;
}
P o i n t fsum ( c o n s t P o i n t &a , c o n s t P o i n t &b ) {
return Point ( a . getx ()+ b . getx ( ) , a . gety ()+ b . gety ( ) ) ;
}
Matrix.hpp Matrix.cpp
c l a s s Matrix { # i n c l u d e " Matrix . hpp"
int l , c ;
double * d a t a ; Matrix : : Matrix ( int l , int c ) : l ( l ) , c ( c ){
public : d a t a = new d o u b l e [ l * c ] ;
Matrix ( int l , int c ) ; }
};
#include <Matrix.hpp>
i n t main(){
Matrix m(3,3);
} / / −> f u i t e memoire , a j o u t d ’ un d e s t r u c t e u r
104
Exemple d’une classe Matrix
Matrix.hpp Matrix.cpp
c l a s s Matrix { # i n c l u d e " Matrix . hpp"
int l , c ;
double * d a t a ; Matrix : : Matrix ( int l , int c ) : l ( l ) , c ( c ){
public : d a t a = new d o u b l e [ l * c ] ;
Matrix ( int l , int c ) ; }
~Matrix ( ) ; Matrix : : ~ Matrix ( ) {
}; delete [] data ;
}
#include <Matrix.hpp>
i n t main(){
Matrix m(3,3);
} / / ok
105
Exemple d’une classe Matrix
Matrix.hpp Matrix.cpp
c l a s s Matrix { # i n c l u d e " Matrix . hpp"
int l , c ;
double * d a t a ; Matrix : : Matrix ( int l , int c ) : l ( l ) , c ( c ){
public : d a t a = new d o u b l e [ l * c ] ;
Matrix ( int l , int c ) ; }
~Matrix ( ) ; Matrix : : ~ Matrix ( ) {
}; delete [] data ;
}
#include <Matrix.hpp>
i n t main(){
Matrix m(3,3);
Matrix n(m);
} / / erreur ! double free ,
/ / => a j o u t d ’ un c o n s t r u c t e u r p a r c o p i e
106
Exemple d’une classe Matrix
Matrix.hpp Matrix.cpp
c l a s s Matrix { # i n c l u d e " Matrix . hpp"
int l , c ;
double * d a t a ; Matrix : : Matrix ( int l , int c ) : l ( l ) , c ( c ){
public : d a t a = new d o u b l e [ l * c ] ;
Matrix ( int l , int c ) ; }
M a t r i x ( c o n s t M a t r i x &x ) ; M a t r i x : : M a t r i x ( c o n s t M a t r i x &x ) : l ( x . l ) , c ( x . c ) {
~Matrix ( ) ; d a t a = new d o u b l e [ l * c ] ;
}; f o r ( i n t i = 0 ; i < l * c ;++ i )
d a t a [ i ]= x . d a t a [ i ] ;
}
Matrix : : ~ Matrix ( ) {
delete [] data ;
}
#include <Matrix.hpp>
i n t main(){
Matrix m(3,3);
Matrix n(m);
} / / ok
107
Exemple d’une classe Matrix
Matrix.hpp Matrix.cpp
c l a s s Matrix { # i n c l u d e " Matrix . hpp"
int l , c ;
double * d a t a ; Matrix : : Matrix ( int l , int c ) : l ( l ) , c ( c ){
public : d a t a = new d o u b l e [ l * c ] ;
Matrix ( int l , int c ) ; }
M a t r i x ( c o n s t M a t r i x &x ) ; M a t r i x : : M a t r i x ( c o n s t M a t r i x &x ) : l ( x . l ) , c ( x . c ) {
~Matrix ( ) ; d a t a = new d o u b l e [ l * c ] ;
}; f o r ( i n t i = 0 ; i < l * c ;++ i )
d a t a [ i ]= x . d a t a [ i ] ;
}
Matrix : : ~ Matrix ( ) {
delete [] data ;
}
#include <Matrix.hpp>
i n t main(){
Matrix m(3,3);
Matrix n(m);
m=n;
} / / e r r e u r ! d o u b l e f r e e + f u i t e memoire
108
Plan
Operateurs
109
Opérateurs: définition
a+b*c;
Dans le cas où a n’est pas un type primitif, cette notation est invalide sauf si
l’on fournit la fonction permettant d’effectuer cette opération.
110
Opérateurs: liste
Unaire: Binaire:
! & * % %= * *=
+ ++ - -= + +=
- -- / /=
~
< <= > >=
spéciaux:
new != == && ||
delete
[ ] & &= | |=
() ^ ^= << <<=
conversion >> >>=
111
Opérateurs: conditions
Pour pouvoir écrire un opérateur, au moins une des opérandes de cet opérateur
doit correspondre à un type utilisateur (pas primitif). Par exemple, on ne peut
pas modifier l’addition sur les entiers.
112
Opérateurs: syntaxe
C’est le premier paramètre qui est prioritaire pour la résolution. Dans le cas
d’une méthode, le premier paramètre (correspondant à this dans la méthode)
sera une référence du type de la classe.
c l a s s A{
public:
i n t o p e r a t o r +( i n t i);
};
Il faut voir cette méthode comme définissant l’opération + entre une instance de
A non constante et un entier.
113
Opérateurs: syntaxe
i n t main(){
Dix g;
i n t douze=g+2;
douze= o p e r a t o r +(g,2);
o p e r a t o r +(2,g); / / E r r e u r !
}
114
Opérateurs: syntaxe
i n t main(){
Dix g;
i n t douze=g+2;
douze=g. o p e r a t o r +(2);
2. o p e r a t o r +(g); / / E r r e u r !
}
115
Opérateurs: précédence
A o p e r a t o r +( c o n s t A&, c o n s t B&);
B o p e r a t o r *( c o n s t A&, c o n s t A&);
i n t main(){
A x;
B y;
x+y*x; / / E r r e u r !
(x+y)*x; / / ok
}
116
Opérateurs: unaires
c l a s s Point{
i n t x,y;
public:
Point o p e r a t o r -() c o n s t {
r e t u r n Point(-x,-y);
}
};
117
Opérateurs: binaires
On écrira les opérateurs binaires dans la classe si les deux opérandes sont de
même type et que l’on a accès à la classe.
c l a s s Point{
i n t x,y;
public:
Point o p e r a t o r +( c o n s t Point &e) c o n s t {
r e t u r n Point(x+e.x,y+e.y);
}
};
118
Opérateurs: binaires
c l a s s Point{
i n t x,y;
...
};
Point o p e r a t o r +( c o n s t Point &p, c o n s t Entier &e){
r e t u r n Point(p.getx()+e.v(),p.gety()+e.v());
}
Double o p e r a t o r +( c o n s t Entier &f, c o n s t Point &q){
r e t u r n q+f; / / o p e r a t o r +(q , f )
}
119
Opérateurs: conception
120
Opérateurs: iostream
L’opérateur << est utilisé pour écrire dans un flux de type std::ostream:
i n t main(){
i n t i;
std::cout<<"un e n t i e r : "<<i<<std::endl; / / 1
...
f<<1; / / 2
f<<i<<1; / / 3
i n t main(){
i n t i;
std::cout<<"un e n t i e r : "<<i<<std::endl; / / 1
...
f<<1; / / 2
f<<i<<1; / / 3
le type de retour de l’opérateur << dans ce cas doit être compatible avec le
premier argument.
123
Opérateurs: iostream
std::cout<<"un e n t i e r : "<<i<<std::endl; / / 1
f<<1; / / 2
f<<i<<1; / / 3
Dans le cas 2, est-ce que l’on est en train de décaler un entier ou bien d’afficher
1 dans un flux ?
Dans le cas 3, est-ce:
• f décalé de i puis de 1 ?
• i décalé de 1 affiché dans f ?
Erreur à la compilation:
c l a s s Point{
i n t x,y;
public:
i n t getx() c o n s t { r e t u r n x;}
i n t gety() c o n s t { r e t u r n y;}
};
128
Solution
c l a s s Point{
i n t x,y;
public:
f r i e n d std::ostream & o p e r a t o r <<(
std::ostream &stream,
c o n s t Point &p);
};
std::ostream & o p e r a t o r <<(std::ostream &stream,
c o n s t Point &p){
stream<<p.x<< ’ / ’<<p.y;
r e t u r n stream;
}
class Point {
int x , y ;
public :
P o i n t ( i n t x =0 , i n t y = 0 ) : x ( x ) , y ( y ) { }
b o o l o p e r a t o r ==( c o n s t P o i n t &p ) c o n s t {
r e t u r n ( x==p . x ) && ( y==p . y ) ;
}
b o o l o p e r a t o r < ( c o n s t P o i n t &p ) c o n s t {
if ( x<p . x ) r e t u r n t r u e ;
i f ( x==p . y ) r e t u r n y<p . y ;
return f a l s e ;
}
/ / . . . <= > >= != . . .
P o i n t o p e r a t o r + ( c o n s t P o i n t &p ) c o n s t {
r e t u r n P o i n t ( x+p . x , y+p . y ) ;
}
P o i n t&o p e r a t o r +=( c o n s t P o i n t &p ) {
x+=p . x ; y+=p . y ;
return * t h i s ;
}
/ / . . . − −= * *= . .
};
130
Opérateur: d’affectation
131
Opérateur: d’affectation
Dans les deux cas, la copie est effectuée attribut par attribut dans l’ordre de leur
déclaration.
La forme 1 est utilisée si chaque attribut possède un opérateur d’affectation en
forme 1.
Si l’on déclare explicitement un opérateur d’affectation en forme 1 ou 2 alors le
compilateur n’ajoute plus l’opérateur. 132
Opérateur: d’affectation
133
Matrix
Matrix.hpp Matrix.cpp
c l a s s Matrix { # i n c l u d e " Matrix . hpp"
int l , c ;
double * d a t a ; Matrix : : Matrix ( int l , int c ) : l ( l ) , c ( c ){
public : d a t a = new d o u b l e [ l * c ] ;
Matrix ( int l , int c ) ; }
M a t r i x ( c o n s t M a t r i x &x ) ; M a t r i x : : M a t r i x ( c o n s t M a t r i x &x ) : l ( x . l ) , c ( x . c ) {
M a t r i x &o p e r a t o r = ( c o n s t M a t r i x &m ) ; d a t a = new d o u b l e [ l * c ] ;
~Matrix ( ) ; f o r ( i n t i = 0 ; i < l * c ;++ i )
}; d a t a [ i ]= x . d a t a [ i ] ;
}
M a t r i x &M a t r i x : : o p e r a t o r = ( c o n s t M a t r i x &m) {
À partir du moment où une i f ( t h i s ==&m) r e t u r n * t h i s ;
delete [] data ; / / libération
classe effectue de l’allocation c=m. c ; l =m. l ; / / re − i n i t i a l i s a t i o n
d a t a =new d o u b l e [ c * l ] ;
dynamique, on écrira f o r ( i n t i = 0 ; i < l * c ;++ i )
d a t a [ i ] =m. d a t a [ i ] ;
systématiquement un }
Matrix : : ~ Matrix ( ) {
constructeur par recopie, un delete [] data ;
}
destructeur et un opérateur
d’affectation
134
Opérateur: foncteur
c l a s s Greater{
public:
b o o l o p e r a t o r ()( i n t a, i n t b);
b o o l o p e r a t o r ()( d o u b l e a, d o u b l e b);
b o o l o p e r a t o r ()( c o n s t char *a, c o n s t char *b);
};
...
Greater greater;
i f (greater(1,0)){ ... }
i f (greater("aba","abb")){...}
138
Opérateur: de conversion
Cette approche est plus logique (que l’implémentation de <<) et permet plus de
139
possiblités.
Opérateurs: autres
142
Opérateur: conclusion
143
Plan
Héritage, polymorphisme
144
Héritage: intro
• ajout de comportements
• ajout de la visibilité
• méthodes d’initialisation (constructeurs) et de libération (destructeurs)
• contrôle de la copie
145
Héritage: définition
En C++, les membres hérités et la relation de typage qu’il existe entre T et Base
ne sont pas necessairement visible de l’extérieur.
146
Héritage: exemple
TuyauPer tper;
Tuyau &tuyau=tper; // r e l a t i o n de t y p a g e
tper.densite(); // ok
tuyau.densite(); // erreur !
tper.debit(); // ok
147
Héritage: pointeurs et référence
TuyauPer tper;
Tuyau t=tper; / / E r r e u r !
Tuyau *p=&tper; / / ok
Tuyau &r=tper; / / ok
TuyauPer &r2=r; / / E r r e u r !
148
Héritage: appel de méthodes
c l a s s A{ C c;
public : c.f(1) ; / / =>B
v o i d f ( char ) { p u t s ( "A\ n" ) ; } c.f( ’ a ’); / / =>B
}; c.A::f( ’A ’); / / => A
c l a s s B : p u b l i c A{ c.B::f( ’ a ’); / / => B
public : B &b=c;
v o i d f ( i n t ) { p u t s ( "B \ n" ) ; } b.f( ’ a ’); / / => B
};
c l a s s C: public B{ } ;
c l a s s A{ c l a s s B : p u b l i c A{
public: public:
v o i d m(){} v o i d m(){}
}; };
B b;
A a;
A &r=b;
b.m(); / / b e s t de t y p e B => B : : m ( )
a.m(); / / a e s t de t y p e A => A : : m ( )
r.m(); / / r e s t de t y p e A => A : : m ( )
A *p=&b;
p->m(); / / p e s t de t y p e A * => A : : m ( )
150
Héritage: et mangling
$g++ -c /tmp/mang.c
$nm mang.o
U __gxx_personality_v0
00000000 T _ZN1A1mEv
00000006 T _ZN1B1mEv
152
Question
153
Héritage: et constructeur
c l a s s Mere{
Fille f;
public:
/*
Mere(){puts("Mere");}
Affiche :
};
c l a s s Fille: p u b l i c Mere{
Mere
public:
Fille
Fille(){puts(" F i l l e ");}
}; */
154
Héritage: et constructeur
c l a s s Mere{
public: Fille f;
Mere( i n t i){puts("Mere");} /*
}; Affiche :
c l a s s Fille: p u b l i c Mere{
public: Mere
Fille():Mere(1) Fille
{puts(" F i l l e ");} */
};
155
Héritage: et constructeur
c l a s s Mere{
public:
Mere( i n t i){
Fille f;
printf("Mere : %d \ n",i);
/*
}
Affiche :
};
c l a s s Fille: p u b l i c Mere{
Mere : −1479451508
i n t v;
Fille
public:
Fille():v(1),Mere(v) */
{puts(" F i l l e ");}
};
Si l’on compile avec -Wall, le compilateur nous signal l’inversion, sinon rien
n’est dit.
156
Héritage: et destructeur
{
c l a s s Mere{
Fille f;
public:
}
Mere(){puts("Mere");}
/*
~Mere(){puts("~Mere");}
Affiche :
};
c l a s s Fille: p u b l i c Mere{
Mere
public:
Fille
Fille(){puts(" F i l l e ");}
~Fille
~Fille(){puts("~ F i l l e ");}
~Mere
};
*/
157
Héritage: et opérateur d’affectation
Dans le cas où nous voulons écrire notre propre opérateur, c’est à nous
d’appeler explicitement l’opérateur d’affectation de la classe Mere.
159
Héritage: et opérateur d’affectation
c l a s s A{
public:
c o n s t A& o p e r a t o r =( c o n s t A&){
puts("A=A"); r e t u r n * t h i s ;}
};
c l a s s M{ F f,g;
A a;
}; f=g;
c l a s s F: p u b l i c M{ / / Pas d ’ a f f i c h a g e !
public:
c o n s t F& o p e r a t o r =( c o n s t F&f){
return * t h i s ;
}
};
160
Héritage: et opérateur d’affectation
c l a s s A{
public:
c o n s t A& o p e r a t o r =( c o n s t A&){
puts("A=A"); r e t u r n * t h i s ;}
};
c l a s s M{
F f,g;
A a;
};
f=g;
c l a s s F: p u b l i c M{
/ / A f f i c h e A=A
public:
c o n s t F& o p e r a t o r =( c o n s t F&f){
M:: o p e r a t o r =(f);
return * t h i s ;
}
};
M m;
m.f();
162
C++11 et delete
A a1(1), a2(2);
a1 = a2; / / E r r o r
A a3(a2); / / E r r o r
Dans le cas de l’héritage public, la relation entre T et Base est visible de tous.
Les méthodes public de Base sont public dans T et les méthodes protected sont
protected dans T.
Dans le cas de l’héritage protected, les méthodes public et protected de Base sont
protected dans T.
Dans le cas de l’héritage private , les méthodes public et protected de Base sont
private dans T.
164
Héritage: public, protected et private
Si l’héritage est publique, tout le monde voit les méthodes publiques de Base à
travers T. Ainsi on pourra toujours voir un T comme un Base.
Si l’héritage est privé alors seules les fonctions/classes amies pourront voir un T
comme un Base.
165
Héritage: public, protected et private
class A{
B b;
...
A&a=b; / / ok
};
class B: p u b l i c A{
C c;
...
A *p=&c; / / e r r e u r !
};
class C: p r o t e c t e d A{
v o i d D::f(){
...
A &r=* t h i s ; / / ok
};
}
class D: p u b l i c C{
...
v o i d F::f(){
};
A &r=* t h i s ; / / e r r e u r
class E: p r i v a t e A{
}
...
};
v o i d E::f(){
class F: p u b l i c E{
A &r=* t h i s ; / / ok
...
}
}; 166
Héritage: public, protected et private
Si l’on souhaite que cette “délégation” soit accessible aux sous classes alors on
utilise l’héritage protégé sinon on utilise l’héritage privé.
167
Héritage: et polymorphisme
Ceci est implanté grace à un attribut caché appelé vtable. C’est un pointeur
vers une table référençant toutes les méthodes pour lesquelles le
polymorphisme doit être mis en oeuvre.
168
Héritage: et virtual
Tout d’abord il nous faut indiquer quelles sont les méthodes “virtuelles”, c’est à
dire pour lesquelles on veut du polymorphisme.
c l a s s Base {
public:
v i r t u a l std::string toString() c o n s t {
r e t u r n " Base ";
}
};
Ceci crée une table associée à la classe Base. Cette table est une table de
pointeurs de fonctions: les méthodes virtuelles
169
Héritage: vtable
Base b;
170
Question
Chaque instance d’une classe ayant des méthodes virtuelles possède une
table des méthodes virtuelles?
A) vrai
B) faux
171
Héritage: vtable
172
Héritage: vtable
Base b;
F f;
Une table des fonctions virtuelles est créée pour la classe F. Celle-ci est
initialement construite par “recopie” de la table de la classe de base.
173
Héritage: vtable
174
Héritage: vtable
Base b;
F f;
Si une méthode est redéfinie: même nom, même arguments (même type) alors
l’entrée dans la table correspondante à cette méthode est modifiée.
On voit que ce mécanisme ne modifie en rien le code de la méthode, c’est la
façon dont la méthode va être appelée qui va être changé.
175
Héritage: appel d’une méthode virtuelle
Lors de l’appel à une méthode sur une instance, le compilateur commence par
chercher cette méthode dans la classe correspondante au type de la variable sur
laquelle la méthode est appelée.
Fille f;
Base &r=f;
r.toString();
Une fois cette méthode trouvée, il y a deux possibilités: soit la méthode est
virtuelle, soit elle ne l’est pas.
Si elle n’est pas virtuelle, alors c’est la méthode du type de la variable qui est
appelée, dans l’exemple Base::toString.
Si elle est virtuelle, alors c’est la méthode dont l’adresse est dans la vtable qui
est appelée, dans l’exemple cela dépend si elle a été redéfinie.
176
Héritage: vtable
Fille f;
Base &r=f;
r.toString();
177
Héritage: vtable
Fille f;
Base &r=f;
r.toString();
178
Héritage: virtuelle pure
Une méthode est dite “virtuelle pure” si elle n’est pas implémentée dans la
classe où elle est déclarée.
classe Base{
public:
v i r t u a l std::string toString() c o n s t =0;
};
Dans cette exemple, Base n’est donc pas instanciable et toute sous classe de
Base ne sera instanciable que si elle redéfinie la méthode toString.
179
Héritage: virtuelle pure
c l a s s Fille : p u b l i c Base{
public:
v i r t u a l v o i d m( c o n s t Fille &){}
}
La redéfinition d’une méthode consiste à écrire dans une classe dérivée une
méthode qui
181
Question
L’appel à une méthode non virtuelle sur une instance NULL génère
systématiquement un SEGFAULT
A) vrai
B) faux
182
Héritage: et valeur par défaut
En C++, les arguments des fonctions et méthodes peuvent prendre des valeurs
par défaut pour les arguments:
v o i d f( i n t =0){}
f(); / / A p p e l f ( 0 ) ;
Les valeurs par défaut doivent être précisées dans la déclaration, en effet elle
indique au compilateur comment compléter l’appel avec ces valeurs si
nécessaire.
Les valeurs par défaut doivent être données du dernier argument vers le premier
argument afin d’éviter toute ambiguité.
183
Héritage: et valeur par défaut
c l a s s M{
i n t main()
public:
{
v i r t u a l ~M(){}
F x;
v i r t u a l v o i d h( i n t =0)=0;
M &y=x;
};
c l a s s F: p u b l i c M{
y.h(); / / a p p e l de F : : m ( 0 ) ;
public:
x.h(); / / a p p e l de F : : m ( 1 ) ;
v i r t u a l v o i d h( i n t i=1){}
}
};
Bien que l’appel soit virtuel, c’est le type de la variable qui est utilisé pour
connaître les valeurs par défaut des arguments!
184
Héritage: et valeur par défaut
c l a s s M{
i n t main()
public:
{
v i r t u a l ~M(){}
F x;
v i r t u a l v o i d h( i n t =0)=0;
M &y=x;
};
c l a s s F: p u b l i c M{
y.h(); / / a p p e l de F : : m ( 0 ) ;
public:
x.h(); / / E r r e u r !
v i r t u a l v o i d h( i n t i){}
}
};
185
Héritage: et using
Lors de l’appel d’une méthode m sur une variable v de type V (ptr ou ref)
pointant sur une instance de type I, la méthode est cherchée de la manière
suivante:
• Existe-t-il des méthodes de nom m dans la classe V?⇒ using
• Oui (partie 1):
• Si une méthode est compatible: elle est choisie
• Si plusieurs méthodes sont compatibles: ambiguité
• Si aucune méthode n’est compatible: erreur.
• Non (partie 2):
• On cherche m dans la hiérarchie de V. Lorsqu’une méthode est trouvée, on
applique la partie 1.
• Une méthode a été trouvée (sinon erreur).
• Si elle est virtuelle:
• On effectue l’appel en utilisant la vtable
• Si la méthode a été redéfinie dans I alors c’est I::m qui est appelée.
187
• Sinon c’est la méthode du plus proche parent.
Algorithme de résolution: remarque
188
Appel avec opérateur de portée
s t r u c t A{ C z;
virtual v o i d m(){} A &x=z;
}; B &y=z;
s t r u c t B: A{ x.m();
}; z.B::m();
s t r u c t C: B{ y.m();
virtual v o i d m(){} y.B::m();
};
189
Héritage: et using
On peut utiliser le mot clé using pour inclure des méthodes parent dans les
méthodes à inspecter:
c l a s s M{
public:
F x,y;
v o i d h( c o n s t M&);
x.h(y); / / 1 . ok
};
M &p=x,&q=y;
c l a s s F: p u b l i c M{
p.h(q); / / 2 . ok
public:
p.h(x); / / 3 . ok
u s i n g M::h;
x.h(p); / / 4 . ok
v o i d h( c o n s t F&);
};
190
Question
À ce stade du cours, l’utilisation de using dans une classe fille par rapport
à une méthode mère suppose l’usage de la surcharge
A) vrai
B) faux
191
Héritage: classe abstraite
On appel "classe abstraite" une classe ayant au moins une méthode virtuelle
pure.
Une classe abstraite ou interface ne peut pas être utilisée comme type de retour
par recopie ou comme argument par valeur (recopie).
192
Héritage: et interface
193
Héritage: et classe abstraite
194
Héritage: et destructeur
Si une classe va être dérivée alors elle doit avoir un destructeur virtuel
(implémenté):
c l a s s A{
};
c l a s s B{
std::string s;
public:
B():s(" h e l l o "){}
};
...
{
B b;
} / / ok : l e d e s t r u c t e u r de B a p p e l l e l e
/ / d e s t r u c t e u r de l a c l a s s e s t d : : s t r i n g s u r s
195
Héritage: et destructeur
Si une classe va être dérivée alors elle doit avoir un destructeur virtuel
(implémenté):
c l a s s A{
};
c l a s s B{
std::string s;
public:
B():s(" h e l l o "){}
};
...
A *a = new B;
d e l e t e a; / / F u i t e mémoire , l e d e s t u c t e u r de
/ / s t d : : s t r i n g n ’ e s t pas ap pe lé .
196
Héritage multiple
En C++, une classe peut avoir plusieurs classes de base, que celles-ci soient
abstraites ou non:
class I1{ ... };
class I2{ ... };
class B{ ...};
class C: p u b l i c B, p u b l i c I1, p u b l i c I2 {
// i m p l é m e n t a t i o n de t o u t e s l e m é t h o d e s
// v i r t u e l l e s pures .
};
197
Hérite multiple: exemple
198
Héritage multiple
L’opérateur de portée et le using sont très utils pour lever les ambiguités:
c l a s s I1{
public:
v o i d m();
};
c l a s s I2{
public:
v o i d m();
};
c l a s s F: p u b l i c I1, p u b l i c I2{
....
m(); / / E r r e u r !
I1::m(); / / ok
...
};
199
Héritage multiple
L’opérateur de portée et le using sont très utils pour lever les ambiguités:
c l a s s I1{
public:
v o i d m();
};
c l a s s I2{
public:
v o i d m();
};
c l a s s F: p u b l i c I1, p u b l i c I2{
....
u s i n g I2::m;
m(); / / ok
I1::m(); / / ok
...
};
200
Héritage multiple virtuel
Il peut arriver qu’une classe possède plusieurs fois une même classe comme
classe de base:
c l a s s I{ p u b l i c : i n t _value; };
c l a s s I1: p u b l i c I{};
c l a s s I2: p u b l i c I{};
c l a s s C: p u b l i c I1, p u b l i c I2{};
201
Hérite multiple: exemple
202
Héritage multiple: ambiguïté
203
Héritage multiple: virtuel
Dans le cas d’un héritage en losange comme celui que nous venons de voir, les
classes I1 et I2 pourraient vouloir partager leur classe de base I.
Pour cela, on utilise l’héritage virtuel.
L’héritage virtuel permet d’indiquer qu’une classe est prête à partager une de
ces classes de base avec une autre classe qui aurait fait de même:
class I{};
class I1 : p u b l i c v i r t u a l I{};
class I2 : p u b l i c v i r t u a l I{};
class I3 : p u b l i c I {};
class C : p u b l i c I1, p u b l i c I2{};
class D : p u b l i c I1, p u b l i c I3{};
205
Hérite multiple: virtuel
C c;
c.m(); / / ok
D d;
d.m(); / / E r r e u r !
Si l’héritage virtuel concerne une classes de base ayant des attributs, alors un
attribut caché est ajouté par le compilateur (un peu comme la vtable). Il y a
donc un coût mémoire.
207
Plan
Template
208
Template: présentation
209
Template: présentation
On pourra écrire deux types de code générique: des classes génériques et des
fonctions génériques.
Voici un exemple de fonction générique:
t e m p l a t e < c l a s s T>
v o i d swap(T &a, T&b){
T t=a;
a=b;
b=t;
}
Cette fonction effectue l’echange de valeur entre a et b quel que soit leur type.
On voit cependant que a et b doivent être de même type
210
L’utilisation de cette fonction swap se fait comme suit:
t e m p l a t e < c l a s s T>
v o i d swap(T &a, T&b){...}
swap< i n t >(a,b); / / 1
swap(a,b); / / 2
i n t a=0;
swap(a,c); / / 3
i n t b=1;
char c= ’ a ’;
Dans le cas d’une classe, on écrira donc le code directement dans le fichier .hpp
On peut tout à fait séparer les déclarations de l’implémentation:
t e m p l a t e < c l a s s T>
c l a s s Entier{
t e m p l a t e < c l a s s T>
T _value;
Entier<T>::Entier( c o n s t T &t):_value(t)
public:
{}
Entier( c o n s t T &t);
}; 213
Template: classe
On parlera de type complet pour désigner une classe générique dont l’ensemble
des paramètres sont spécifiés.
Il est important de retenir qu’il n’existe aucune relation de typage entre deux
types complets correspondant à une même classe générique.
214
Template: valeur par défaut
On peut spécifier des valeurs par défaut pour les paramètres d’une classe
générique (pas des fonctions). Celles-ci doivent être fournies de la droite vers la
gauche:
t e m p l a t e < c l a s s T= i n t >
c l a s s Entier{...
t e m p l a t e < c l a s s D, c l a s s H= i n t >
c l a s s HashTable{...
Entier e; / / ok : )
215
Template: et type primitif
Un code peut aussi être paramétré par des valeurs (constantes) de types
primitifs:
t e m p l a t e < i n t taille>
c l a s s Matrix{
i n t _data[taille][taille];
public:
};
Dans ce cas, Matrix<4> et Matrix<3> sont deux types différents. Cet exemple
pourrait être utilisé en infographie.
216
Template: spécialisation
217
Template: spécialisation
Dans le cas où les paramètres sont des types primifs, on peut spécialiser selon
les valeurs:
t e m p l a t e < i n t n>
v o i d affiche(){
printf("%d \ n",n);
}
t e m p l a t e <>
v o i d affiche<0>(){
printf(" z e r o \ n");
}
...
affiche(); / / e r r e u r !
affiche<1>(); / / a f f i c h e : 1
affiche<0>(); / / a f f i c h e : z e r o
218
Template: spécialisation
Dans le cas des classes, la spécialisation peut aussi se faire sur les pointeurs:
t e m p l a t e < c l a s s T>
c l a s s Tableau{
T *_data;
...
};
t e m p l a t e < c l a s s T>
c l a s s Tableau<T *>{
T **_data; / / A t t e n t i o n i c i !
public:
Tableau( i n t i){
...
_data[i]=NULL;
...
}
};
219
Template: spécialisation partielle
t e m p l a t e < c l a s s T, c l a s s H>
c l a s s HashTable<T *,H>{
};
t e m p l a t e < c l a s s T>
c l a s s HashTable<T *, i n t >{
};
220
Template: contraintes
Lorsque l’on utilise un type dans un code générique, il faut faire très attention
aux propriétés que l’on impose sur ce type:
t e m p l a t e < c l a s s T>
t e m p l a t e < c l a s s T>
v o i d swap(T &a,T&b){
v o i d swap(T &a,T&b){
T t;
T t=a;
t=a;
a=b;
a=b;
b=t;
b=t;
}
}
On veillera à toujours indiquer clairement les méthodes attendues pour une type
paramètre.
221
Template: STL et BOOST
La STL contient:
222
Template: STL
Pour comprendre et utiliser la STL, il faut savoir qu’il est possible de déclarer
un alias de type dans une classe:
t e m p l a t e < c l a s s T>
c l a s s Entier{
public:
t y p e d e f T type;
};
Entier< i n t >::type i;
223
Template: STL
Parmis les conteneurs les plus connus, il y a les vecteurs, ceux-ci répondent au
concept de “Conteneur à Accès Quelconque” et “Sequence avec insertion en
fin”. Ces concepts définissent un certain nombre des propriétés: compléxité,
nom de méthodes, type interne...
# i n c l u d e <vector>
std::vector< i n t > v;
v.push_back(0);
v.push_back(1);
v[0]=2;
f o r (std::vector< i n t >::iterator i=v.begin();
i!=v.end();
i++)
std::cout<<" "<<*i;
Les itérateurs ont été conçus pour fonctionner comme les pointeurs: on utilise
++ et -- pour se déplacer, * et -> pour utiliser la valeur représentée par
l’itérateur.
Voici un exemple permettant de trier un vecteur:
# i n c l u d e <vector>
vector< i n t > v;
v.push_back(0); v.push_back(5); v.push_back(2);
std::sort(v.begin(),v.end());
228
Template: et metaprogrammation
t e m p l a t e < i n t a>
s t r u c t Puissance<a,0>{
s t a t i c c o n s t i n t resultat=1;
};
229
Template: et metaprogrammation
• les templates,
• la spécialisation,
• les types internes,
• les attributs statiques et constants
230
Template: et metaprogrammation
On peut ainsi écrire un “language” sur les types, avec des branchements
conditionnels:
t e m p l a t e < b o o l C, c l a s s T, c l a s s E>
s t r u c t IfThenElse{
t y p e d e f T result;
};
t e m p l a t e < c l a s s T, c l a s s E>
s t r u c t IfThenElse< f a l s e ,T,E>{
t y p e d e f E result;
};
231
Template: et metaprogrammation
Comparaison de types:
t e m p l a t e < c l a s s T, c l a s s U>
s t r u c t Equal{
s t a t i c b o o l result= f a l s e ;
};
t e m p l a t e < c l a s s T>
s t r u c t Equal<T,T>{
s t a t i c b o o l result= t r u e ;
}
b o o l b=Equal< i n t ,size_t>::result;
IfThenElse<Equal< i n t ,ssize_t>::result,
int ,
long >::result i;
232
Template: et metaprogrammation
233
Template: et metaprogrammation
On peut aussi utiliser les fonctions templates pour inférer des types et “cacher”
les templates
struct Interface {
virtual ~ Interface (){}
....
};
t e m p l a t e < c l a s s T>
c l a s s MaClasse : p u b l i c I n t e r f a c e {
t y p e d e f I f T h e n E l s e < I s P o i n t e r <T > : : r e s u l t , l i s t <T> , v e c t o r <T>> i c o n t a i n e r ;
i c o n t a i n e r _data ;
...
public :
MaClasse ( c o n s t T &t ) {
....
}
...
};
t e m p l a t e < c l a s s U>
s t a t i c MaClasse <U> * g e t C l a s s e ( c o n s t U &v a l u e ) {
r e t u r n new MaClasse <U> ( v a l u e ) ;
}
i n t main ( ) {
Interface * i=getClasse (0); 234
}
Template: et metaprogrammation
s t r u c t RandomContainer{};
s t r u c t ReversibleContainer{};
t e m p l a t e < c l a s s T, c l a s s Type>
s t r u c t Container{
t y p e d e f vector<T> result;
}
t e m p l a t e < c l a s s T>
s t r u c t Container<T,ReversibleContainer>{
t y p e d e f list<T> result;
}
235
Plan
Synthèse
236
Synhèse
• l’encapsulation (visibilité)
• les références
• les opérateurs
• l’héritage simple et multiple
• les fonctions vituelles
• la programmation générique
237
Synthèse
238
Ecrire du code lisible
239
Ecrire du code lisible
• Destructeur virtuel
• méthodes toutes virtuelles
• Heritage virtuel sur les interfaces
• Limiter l’usage de l’héritage multiple.
• pas d’opérateurs
• pas de surcharge
Pour les parties calculatoires (pixels d’une image, graphes, etc...) on utilise en
priorité:
241