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