MIAGE III — Année 2003/2004

Transcription

MIAGE III — Année 2003/2004
MIAGE III — Année 2003/2004
Spécificités du Langage C++
classes et méthodes amies
surcharge de fonctions et d’opérateurs
C++ – p.1/20
Classes et méthodes amies
Certaines méthodes ou classes nécessitent un accès illimité aux attributs
d’une autre classe. Ce n’est pas toujours du à un problème de conception
des classes. On parle de méthode amie (resp. classe amie).
Syntaxe d’une méthode amie :
class A {
...
friend void affiche(const A &); // déclare une fonction amie
public :
int _x;
private:
int _y;
};
...
void affiche(const A &a) {
std::cout << a._x;
// la fonction amie accède librement
std::cout << a._y;
// à tous les attributs et méthodes de A
}
C++ – p.2/20
Classes amies
Syntaxe d’une classe amie :
class B;
// déclaration anticipée
class A {
friend class B;
// déclare la classe B comme amie de A
private :
int _x;
int _y;
};
...
A a;
class B {
// B a donc librement accès à tous
public:
// les attributs et méthodes de A
B() { a._y = 5; }
void toto(int x) { a._x = x; }
};
Equivalent à définir les (méthodes/attributs) de la classe B comme amies.
C++ – p.3/20
Les amis : cas d’utilisation
lorsqu’on veut une executer une fonction qui n’est pas une méthode,
mais qui doit avoir accès aux attributs
exemple du calcul du produit vectoriel prodvect() sur deux
vecteurs : il est plus naturel d’écrire c = prodvect(a,b) que c
= a.prodvect(b)
class Vecteur2D {
...
friend Vecteur2D prodvect(const Vecteur2D &a, const Vecteur2D &b);
};
...
Vecteur2D prodvect(const Vecteur2D &a, const Vecteur2D &b) {...}
...
Vecteur2D v1,v2,v3;
v1 = prodvect(v2,v3);
C++ – p.4/20
Les amis de nos amis...
Attention cependant :
l’amitié n’est pas transitive (les amis de nos amis sont pas nos amis)
class A {
friend class
int _x;
};
class B {
friend class
int _y;
};
class C {
titi(const B
toto(const A
};
B;
C;
&b) { b._y = 12;}
&a) { a._x = 12;}
// oui
// non
l’amitié ne se s’hérite pas (les amis de nos parents sont pas nos
amis)
class A {
friend class Z;
int _x;
};
class B : public A {
}
class Z {
toto(const A &a) { a._x = 12; }
titi(const B &b) { b._x = 12; }
}
// oui
// non
C++ – p.5/20
Surcharge
En C++ (et à la différence du C) les fonctions et méthodes sont définies
par leur nom, leur signature (arguments) et leur caractère const. Cela
permet le mécanisme de syrcharge.
plusieurs méthodes peuvent avoir le même nom
le nombre et le type des arguments différencie les méthodes
le mot clé const différencie aussi les méthodes
le type de retour n’est pas considéré.
class A {
public:
A(int x=0); // valeur par défaut : identique que déclarer A() et A(int)
void toto(int x) const; // effectue des traitements différents
void toto(int x);
// suivant si l’objet est const ou non
void toto(double x);
void toto(int x, int y);
void toto(double x, double y) const;
};
...
A a;
a.toto(5);
const A &aa = a;
aa.toto(5);
aa.toto(5,6);
// appel à toto(int x)
// appel à toto(int x) const car ’aa’ est constant
// appel à toto(double, double) const (avec cast int->double)
C++ – p.6/20
Surcharge des opérateurs
Inspiré de l’Algol68, le langage C++ offre le paradigme de surcharge des
opérateurs pour les classes définies par l’utilisateur. Les opérateurs
surchargeables sont :
opérations arithmétiques +,-,/,*,%,ˆ et +=,-=,...
opérations logiques |, ||, &, &&, ! et |=,&=
comparaisons ==, !=, <=, >=, <, >
opérateur sur les flux <<, >>, <<=, >>=
opérations d’incrémentation et de décrémentation ++, -déreférencement *, sélection de membre par pointeur ->
opérateur d’indicage [] et fonctionnel ()
opérateur de gestion dynamique de mémoire new,new[],delete,delete[]
et la virgule , !
Les opérateurs non-surchargeables :
résolution de portée (::), sélection de membre (.), .*
opérations de cast static cast, dynamic cast, const cast,
reinterpret cast
C++ – p.7/20
Opérateurs internes
Les opérateurs internes sont des membres de la classe :
class Complex {
public :
Complex();
Complex(double r, double i);
Complex operator +(const Complex &c) {
_re += c._re;
_im += c._im;
return *this;
// retourne une copie de l’objet
}
// ce qui permet d’écrire a+b+c;
Complex operator * (const Complex &c) { ... }
private:
double _re, _im;
};
...
Complex a,b,c;
a = b+c;
// appel de b.operator+(c) (modifie b!)
b = b+c*a;
// priorité standard b+(c*a) (modifie c et b)
c = Complex(0.0,1.0) + Complex(3.0, 4.0);
Attention, dans ce cas précis, on modifie l’objet sur lequel porte l’opération!
C++ – p.8/20
Opérateurs externes
Les opérateurs externes ne sont pas membres de la classe :
class Complex {
public :
Complex();
Complex(double r=0.0, double i=0.0);
...
friend Complex operator+(const Complex &c1, const Complex &c2);
};
Complex operator+(const Complex &c1, const Complex &c2) {
return Complex(c1._re+c2._re, c1._im+c2._im);
}
évite la tentation de modifier l’objet
l’opérateur devient symétrique (ce qui n’est pas le cas avec les
opérateurs internes)
Complex a,b;
a = b+1.0;
a = 1.0+b;
// conversion implicte de double vers Complex
// ne fonctionne qu’avec un opérateur externe
C++ – p.9/20
Les opérateurs spécifiques
opérateur d’affectation (déjà vu)
opérateur de transtypage
opérateur fonctionnel
opérateur d’indicage
opérateur de flux
opérateur d’indirection et de déréférencement
opérateur d’allocation/désallocation dynamique
C++ – p.10/20
L’opérateur d’affectation
class A {
public :
A();
A& operator=(const A &a);
...
};
nécessaire dans des classes qui gèrent des attributs
dynamiquement alloués.
attention à libérer correctement les ressources
attention à la duplication des instructions
attention à l’auto-affectation (a=a)
Règle des trois : si vous surchargez l’opérateur d’affectation, surchargez
aussi le constructeur par recopie et le destructeur.
C++ – p.11/20
L’opérateur de transtypage
Il est possible de surcharger les opérateurs de transtypage pour effectuer
des conversions implicites.
Exemple :
class MaChaine {
public :
MaChaine();
...
operator char *(void) const; // operation de conversion vers un (char *)
...
};
...
MaChaine mc;
char *s = mc;
// appel à l’operateur ‘‘operator char *(void)’’
char chn[80];
strcpy(chn, mc); // appel à l’operateur ‘‘operator char *(void)’’
préférez-leur les constructeurs dans les classes cibles (quand c’est
possible)
C++ – p.12/20
L’opérateur fonctionnel
Le C++ permet la surcharge de l’opérateur d’appel de fonctions (), ce qui
offre une utilisation fonctionnelle des objets. On parle de foncteurs.
Exemple:
// definition d’une matrice de réels
class Matrice {
...
double &operator(unsigned int i, unsigned int j);
double operator(unisigned int i, unsigned int j) const;
...
};
...
Matrice m;
m(1,1) = 5.0;
// modification de l’élément en position (1,1)
double y = m(3,3); // lecture de l’élément en position (3,3)
permet d’implémenter des opérateurs n-aires
C++ – p.13/20
L’opérateur d’indicage
Le C++ permet la surcharge de l’opérateur d’indicage [].
Exemple:
class MaChaine {
...
char &operator[] (unsigned int i);
char operator[] (unsigned int i) const;
...
};
MaChaine s(‘‘toto’’);
s[1] = ’c’;
char c = s[2];
C++ – p.14/20
Opérateur de flux
Les opérateurs de flux de sortie << et d’entrée >> sont surchargeables.
Exemple :
class A {
friend std::ostream &operator << (std::ostream &s, const A &a);
friend std::istream &operator >> (std::istream &s, A &a);
private:
int _x;
int _y;
};
std::ostream & A::operator << (std::ostream &s, const A &a) {
s << ‘‘Class A :’’ << std::endl;
s << ‘‘ _x =‘‘ << _x << std::endl;
s << ‘‘ _y =‘‘ << _y << std::endl;
return s;
}
std::istream & A::operator >> (std::istream &s, A &a) {
s >> _x >> _y;
return s;
}
...
A a;
std::cout << a;
// affichage à l’écran
std::ofstream os(‘‘toto.txt’’); // ostream : flux de sortie
os << a;
// écriture dans un fichier
std::cin >> a;
// saisie au clavier
std::ifsteam is(‘‘titi.txt’’);
// istream : flux d’entrée
is >> a;
// lecture dans un fichier
C++ – p.15/20
Opérateur indirection & déreférencement
La surcharge de l’opérateur d’indirection & est généralement utilisée
pour retourner une autre adresse que celle de l’objet lui-même.
pratique dans le cas d’encapsulation d’objets : pointeurs
intelligents
possibilité de déclarer l’opérateur comme private afin d’éviter
tout accès à l’adresse de l’objet !
Les opérateurs de déreférencement * et de sélection -> sont
généralement utilisés pour des raisons d’encapsulation (pointeurs
intelligents).
C++ – p.16/20
Opérateur d’allocation/désallocation
À des fins de gestion mémoire performante, il est possible de redéfinir les opérateurs new,
delete, new[] et delete[].
Utilisation comme opérateur de placement :
on alloue un bloc de taille suffisante
les allocations ne font que déplacer un pointer dans le bloc
class Pool {
friend class A;
public :
Pool(size_t n) { _ptr = new char[n]; _index = _ptr;}
~Pool() { delete [] _ptr;}
char *operator & () { return _ptr; }
private:
char *_index;
char *_ptr;
};
class A {
// surcharge de l’opérateur de placement
static void *operator new(size_t taille, Pool *p) {
char *ptr = p->_index;
p->_index += taille;
return ptr;
}
};
Pool p(1000*sizeof(A));
for (int i=0; i < 1000; i++) {
A *pa = new (&p) A();
// alloue 1000 objets en un temps dérisoire
}
// (env. 10 fois moins coûteux que par le new classique)
C++ – p.17/20
Surcharge des opérateurs : bilan
Avantages :
facilite la lisibilité
facilite l’écriture (modification)
Cependant :
attention au coût de construction des objets
temporaires
cache la complexité de l’opération
attention à la cohérence (définir == et !=)
C++ – p.18/20
Surcharge des opérateurs : exemple
On veut créer une structure de données avec des
opérations ensemblistes suivantes :
opération de création d’un ensemble Set t = 1,2,3,4;
opération d’ajout à un ensemble t += 5;
opération d’union de deux ensembles Set u = t + v;
opération de retrait d’un ensemble t /= 1;
opération de différence ensembliste Set u = t / v;
C++ – p.19/20
Solution
class Set {
public:
Set();
Set(int x);
Set &operator,(int x);
Set operator+=(int x)
//
//
//
//
ensemble vide
conversion implicite d’un entier en Set
ajout d’un entier à un ensemble
idem
friend Set operator + (const Set &s1, const Set &s2);
// union ensembliste
Set operator/=(int x);
// retrait d’un élément
friend Set operator / (const Set &s1, const Set &s2);
// différence ensembliste
friend std::ostream &operator << (std::ostream &s, const Set &set);
// affichage
private:
// ici, peu importe l’implémentation ...
};
...
int main() {
Set t;
t = 1,2,3,4
std::cout << t;
t ¯ 1;
Set s;
s = 5,6,7;
s = s + t;
std::cout << s;
return 0;
}
// affiche 1,2,3,4
// affiche 2,3,4,5,6,7
C++ – p.20/20