• Aucun résultat trouvé

Exemple 7-2. Opérateur de résolution de portée int valeur(void) // Fonction globale

7.11. Surcharge des opérateurs

7.11.2. Surcharge des opérateurs externes

m_x += c.m_x;

m_y += c.m_y;

return *this;

}

complexe &complexe::operator-=(const complexe &c) {

m_x -= c.m_x;

m_y -= c.m_y;

return *this;

}

complexe &complexe::operator*=(const complexe &c) {

double temp = m_x*c.m_x -m_y*c.m_y;

m_y = m_x*c.m_y + m_y*c.m_x;

m_x = temp;

return *this;

}

complexe &complexe::operator/=(const complexe &c) {

double norm = c.m_x*c.m_x + c.m_y*c.m_y;

double temp = (m_x*c.m_x + m_y*c.m_y) / norm;

m_y = (-m_x*c.m_y + m_y*c.m_x) / norm;

m_x = temp;

return *this;

}

Note : La bibliothèque standard C++ fournit une classe traitant les nombres complexes de manière complète, la classe complex. Cette classe n’est donc donnée ici qu’à titre d’exemple et ne devra évidemment pas être utilisée. La définition des nombres complexes et de leur principales propriétés sera donnée dans la Section 14.3.1, où la classe complex sera décrite.

Les opérateurs d’affectation fournissent un exemple d’utilisation du pointeur this. Ces opérateurs renvoient en effet systématiquement l’objet sur lequel ils travaillent, afin de permettre des affectations multiples. Les opérateurs de ce type devront donc tous se terminer par :

return *this;

7.11.2. Surcharge des opérateurs externes

Une deuxième possibilité nous est offerte par le langage pour surcharger les opérateurs. La définition de l’opérateur ne se fait plus dans la classe qui l’utilise, mais en dehors de celle-ci, par surcharge d’un opérateur de l’espace de nommage global. Il s’agit donc d’opérateurs externes cette fois.

La surcharge des opérateurs externes se fait donc exactement comme on surcharge les fonctions nor-males. Dans ce cas, tous les opérandes de l’opérateur devront être passés en paramètres : il n’y aura pas de paramètre implicite (le pointeurthisn’est pas passé en paramètre).

Chapitre 7. C++ : la couche objet

La syntaxe est la suivante : type operatorOp(opérandes)

opérandesest la liste complète des opérandes.

L’avantage de cette syntaxe est que l’opérateur est réellement symétrique, contrairement à ce qui se passe pour les opérateurs définis à l’intérieur de la classe. Ainsi, si l’utilisation de cet opérateur nécessite un transtypage sur l’un des opérandes, il n’est pas nécessaire que cet opérande soit obliga-toirement le deuxième. Donc si la classe dispose de constructeurs permettant de convertir un type de donnée en son prope type, ce type de donnée peut être utilisé avec tous les opérateurs de la classe.

Par exemple, les opérateurs d’addition, de soustraction, de multiplication et de division de la classe complexe peuvent être implémentés comme dans l’exemple suivant.

Exemple 7-17. Surcharge d’opérateurs externes

class complexe {

friend complexe operator+(const complexe &, const complexe &);

friend complexe operator-(const complexe &, const complexe &);

friend complexe operator*(const complexe &, const complexe &);

friend complexe operator/(const complexe &, const complexe &);

double m_x, m_y; // Les parties réelles et imaginaires.

public:

// Constructeurs et opérateur de copie : complexe(double x=0, double y=0);

complexe(const complexe &);

complexe &operator=(const complexe &);

// Fonctions permettant de lire les parties réelles // et imaginaires :

double re(void) const;

double im(void) const;

// Les opérateurs de base:

complexe &operator+=(const complexe &);

complexe &operator-=(const complexe &);

complexe &operator*=(const complexe &);

complexe &operator/=(const complexe &);

};

// Les opérateurs de base ont été éludés ici : ...

complexe operator+(const complexe &c1, const complexe &c2) {

complexe result = c1;

return result += c2;

}

complexe operator-(const complexe &c1, const complexe &c2) {

complexe result = c1;

return result -= c2;

}

Chapitre 7. C++ : la couche objet

complexe operator*(const complexe &c1, const complexe &c2) {

complexe result = c1;

return result *= c2;

}

complexe operator/(const complexe &c1, const complexe &c2) {

complexe result = c1;

return result /= c2;

}

Avec ces définitions, il est parfaitement possible d’effectuer la multiplication d’un objet de type plexe avec une valeur de type double. En effet, cette valeur sera automatiquement convertie en com-plexe grâce au constructeur de la classe comcom-plexe, qui sera utilisé ici comme constructeur de transty-page. Une fois cette conversion effectuée, l’opérateur adéquat est appliqué.

On constatera que les opérateurs externes doivent être déclarés comme étant des fonctions amies de la classe sur laquelle ils travaillent, faute de quoi ils ne pourraient pas manipuler les données membres de leurs opérandes.

Note : Certains compilateurs peuvent supprimer la création des variables temporaires lorsque celles-ci sont utilisées en tant que valeur de retour des fonctions. Cela permet d’améliorer grande-ment l’efficacité des programmes, en supprimant toutes les copies d’objets inutiles. Cependant ces compilateurs sont relativement rares et peuvent exiger une syntaxe particulière pour effectuer cette optimisation. Généralement, les compilateurs C++ actuels suppriment la création de vari-able temporaire dans les retours de fonctions si la valeur de retour est construite dans l’instruction returnelle-même. Par exemple, l’opérateur d’addition peut être optimisé ainsi :

complexe operator+(const complexe &c1, const complexe &c2) {

return complexe(c1.m_x + c2.m_x, c1.m_y + c2.m_y);

}

Cette écriture n’est cependant pas toujours utilisable, et l’optimisation n’est pas garantie.

La syntaxe des opérateurs externes permet également d’implémenter les opérateurs pour lesquels le type de la valeur de retour est celui de l’opérande de gauche et que le type de cet opérande n’est pas une classe définie par l’utilisateur (par exemple si c’est un type prédéfini). En effet, on ne peut pas définir l’opérateur à l’intérieur de la classe du premier opérande dans ce cas, puisque cette classe est déjà définie. De même, cette syntaxe peut être utile dans le cas de l’écriture d’opérateurs optimisés pour certains types de données, pour lesquels les opérations réalisées par l’opérateur sont plus simples que celles qui auraient été effectuées après transtypage.

Par exemple, si l’on veut optimiser la multiplication à gauche par un scalaire pour la classe complexe, on devra procéder comme suit :

complexe operator*(double k, const complexe &c) {

complexe result(c.re()*k,c.im()*k);

return result;

}

ce qui permettra d’écrire des expressions du type :

Chapitre 7. C++ : la couche objet complexe c1, c2;

double r;

...

c1 = r*c2;

La première syntaxe n’aurait permis d’écrire un tel opérateur que pour la multiplication à droite par un double. En effet, pour écrire un opérateur interne permettant de réaliser cette optimisation, il aurait fallu surcharger l’opérateur de multiplication de la classe double pour lui faire accepter un objet de type complexe en second opérande...