• Aucun résultat trouvé

Appel de fonction membre statique

int i;

static int j;

public:

static int get_value(void);

};

int Entier::j=0;

int Entier::get_value(void) {

j=1; // Légal.

return i; // ERREUR ! get_value ne peut pas accéder à i.

}

La fonctionget_valuede l’exemple ci-dessus ne peut pas accéder à la donnée membre non statique i, parce qu’elle ne travaille sur aucun objet. Son champ d’action est uniquement la classe Entier. En revanche, elle peut modifier la variable statiquej, puisque celle-ci appartient à la classe Entier et non aux objets de cette classe.

L’appel des fonctions membre statiques se fait exactement comme celui des fonctions membres non statiques, en spécifiant l’identificateur d’un des objets de la classe et le nom de la fonction membre, séparés par un point. Cependant, comme les fonctions membres ne travaillent pas sur les objets des classes mais plutôt sur les classes elles-mêmes, la présence de l’objet lors de l’appel est facultatif. On peut donc se contenter d’appeler une fonction statique en qualifiant son nom du nom de la classe à laquelle elle appartient à l’aide de l’opérateur de résolution de portée.

Exemple 8-15. Appel de fonction membre statique

class Entier {

static int i;

public:

static int get_value(void);

};

int Entier::i=3;

int Entier::get_value(void) {

return i;

}

int main(void) {

// Appelle la fonction statique get_value : int resultat=Entier::get_value();

return 0;

}

Les fonctions membres statiques sont souvent utilisées afin de regrouper un certain nombre de fonc-tionnalités en rapport avec leur classe. Ainsi, elles sont facilement localisable et les risques de conflits

Chapitre 8. C++ : la couche objet de noms entre deux fonctions membres homonymes sont réduits. Nous verrons également dans le Chapitre 11 comment éviter les conflits de noms globaux dans le cadre des espaces de nommage.

8.11. Surcharge des opérateurs

On a vu précédemment que les opérateurs ne se différencient des fonctions que syntaxiquement, pas logiquement. D’ailleurs, le compilateur traite un appel à un opérateur comme un appel à une fonction.

Le C++ permet donc de surcharger les opérateurs pour les classes définies par l’utilisateur, en utilisant une syntaxe particulière calquée sur la syntaxe utilisée pour définir des fonctions membres normales.

En fait, il est même possible de surcharger les opérateurs du langage pour les classes de l’utilisateur en dehors de la définition de ces classes. Le C++ dispose donc de deux méthodes différentes pour surcharger les opérateurs.

Les seuls opérateurs qui ne peuvent pas être surchargés sont les suivants : ::

. .*

?:

sizeof typeid static_cast dynamic_cast const_cast reinterpret_cast

Tous les autres opérateurs sont surchargeables. Leur surcharge ne pose généralement pas de problème et peut être réalisée soit dans la classe des objets sur lesquels ils s’appliquent, soit à l’extérieur de cette classe. Cependant, un certain nombre d’entre eux demandent des explications complémentaires, que l’on donnera à la fin de cette section.

Note : On prendra garde aux problèmes de performances lors de la surcharge des opérateurs.

Si la facilité d’écriture des expressions utilisant des classes est grandement simplifiée grâce à la possibilité de surcharger les opérateurs pour ces classes, les performances du programme peu-vent en être gravement affectées. En effet, l’utilisation inconsidérée des opérateurs peut conduire à un grand nombre de copies des objets, copies que l’on pourrait éviter en écrivant le programme classiquement. Par exemple, la plupart des opérateurs renvoient un objet du type de la classe sur laquelle ils travaillent. Ces objets sont souvent créés localement dans la fonction de l’opérateur (c’est-à-dire qu’ils sont de portéeauto). Par conséquent, ces objets sont temporaires et sont détruits à la sortie de la fonction de l’opérateur. Cela impose donc au compilateur d’en faire une copie dans la valeur de retour de la fonction avant d’en sortir. Cette copie sera elle-même détruite par le compilateur une fois qu’elle aura été utilisée par l’instruction qui a appelé la fonction. Si le résultat doit être affecté à un objet de l’appelant, une deuxième copie inutile est réalisée par rapport au cas où l’opérateur aurait travaillé directement dans la variable résultat. Si les bons compilateurs sont capables d’éviter ces copies, cela reste l’exception et il vaut mieux être averti à l’avance plutôt que de devoir réécrire tout son programme a posteriori pour des problèmes de performances.

Nous allons à présent voir dans les sections suivantes les deux syntaxes permettant de surcharger les opérateurs pour les types de l’utilisateur, ainsi que les règles spécifiques à certains opérateurs particuliers.

Chapitre 8. C++ : la couche objet

8.11.1. Surcharge des opérateurs internes

Une première méthode pour surcharger les opérateurs consiste à les considérer comme des méthodes normales de la classe sur laquelle ils s’appliquent. Le nom de ces méthodes est donné par le mot cléoperator, suivi de l’opérateur à surcharger. Le type de la fonction de l’opérateur est le type du résultat donné par l’opération, et les paramètres, donnés entre parenthèses, sont les opérandes. Les opérateurs de ce type sont appelés opérateurs internes, parce qu’ils sont déclarés à l’intérieur de la classe.

Voici la syntaxe :

type operatorOp(paramètres) l’écriture

A Op B

se traduisant par : A.operatorOp(B)

Avec cette syntaxe, le premier opérande est toujours l’objet auquel cette fonction s’applique. Cette manière de surcharger les opérateurs est donc particulièrement bien adaptée pour les opérateurs qui modifient l’objet sur lequel ils travaillent, comme par exemple les opérateurs =, +=, ++, etc. Les paramètres de la fonction opérateur sont alors le deuxième opérande et les suivants.

Les opérateurs définis en interne devront souvent renvoyer l’objet sur lequel ils travaillent (ce n’est pas une nécessité cependant). Cela est faisable grâce au pointeurthis.

Par exemple, la classe suivante implémente les nombres complexes avec quelques-unes de leurs opé-rations de base.

Exemple 8-16. Surcharge des opérateurs internes

class 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 &);

};

complexe::complexe(double x, double y)

Chapitre 8. C++ : la couche objet

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;

Chapitre 8. C++ : la couche objet 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;

8.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).

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

où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.