Apprendre C++ avec Qt : Leon 11 Fonctions oprateur
1 - Gnralits.......................................................................................................................... 2 Quelles fonctions oprateur pouvons-nous dfinir ? ........................................................2 Immuabilit de la prcdence des oprateurs et de leur arit...........................................3 Particularits des fonctions oprateur .............................................................................3 Dans quels cas est-il judicieux de surcharger un oprateur ? ..........................................3 2 - Surcharger les oprateurs monadiques ............................................................................... 3 Les oprateurs prfixs....................................................................................................4 Les oprateurs ++et - - suffixs ......................................................................................4 L'oprateur - >.................................................................................................................5 3 - Surcharger les oprateurs dyadiques .................................................................................. 6 Fonction oprateur globale ..............................................................................................6 Fonction oprateur membre d'une classe.........................................................................7 4 - Un cas particulier : l'oprateur "parenthses"...................................................................... 7 Fonctionodes..................................................................................................................8 Oprateurs de transtypage ..............................................................................................8 5 - Oprateurs sur enum: un exemple ...................................................................................... 9 Conversion d'une valeur entire en valeur de type EJ our ...............................................10 Reprsentation crite de la valeur d'un EJ our ...............................................................10 Fonctions oper at or ++( ) .............................................................................................10 Utilisation du type EJ our ..............................................................................................11 6 - Bon, c'est gentil tout a, mais a fait dj 9 pages. Qu'est-ce que je dois vraiment en retenir ?............................................................................................................................ 11 Document du 05/06/07 - Retrouvez la version la plus rcente sur http://www.up.univ-mrs.fr/wcpp C++ - Leon 11 Fonctions oprateur 2/12 La syntaxe normale pour invoquer une fonction est de mentionner son nom et de faire suivre celui-ci d'un couple de parenthses entourant les variables ou valeurs que la fonction doit utiliser pour initialiser ses paramtres. Cette syntaxe n'est pas toujours la plus agrable que l'on puisse imaginer, notamment lorsque la fonction ralise une opration qui est habituellement dsigne par un symbole bien connu. Imaginons par exemple que nous avons dfini une classe CTr uc pour laquelle la notion d'addition a un sens. Il est, bien entendu, possible de dfinir une fonction membre nomme aj out e( ) , qui permettra d'crire
/ / pr emi er Tr uc, secondTr uc et t r ucSomme sont des i nst ances de CTr uc t r ucSomme = pr emi er Tr uc. aj out e( secondTr uc) ;
Toutefois, notre code serait sans doute plus lisible si nous pouvions crire
t r ucSomme = pr emi er Tr uc + secondTr uc;
C'est justement cette possibilit que nous offre le langage C++ en nous autorisant dfinir les fonctions qui seront excutes pour valuer les expressions comportant certains oprateurs. 1 - Gnralits Le code dfinissant le traitement qui doit correspondre un oprateur va prendre place dans ce que l'on appelle une "fonction oprateur". La dfinition d'une telle fonction est aussi souvent qualifie de surcharge de l'oprateur en question. Cette surcharge est soumise quelques restrictions, mais ne diffre finalement qu'assez peu de la dfinition d'une fonction "ordinaire". Quelles fonctions oprateur pouvons-nous dfinir ? En dpit de sa grande gnrosit, C++ ne permet pas de crer de nouveaux oprateurs, mais seulement de dfinir la faon dont certains de ses oprateurs peuvent s'appliquer de nouveaux types d'oprandes. Nous ne pourrons donc jamais crire des choses comme :
i f ( a <> b) / / i l n' exi st e pas d' opr at eur <> en C++ 1 c = a # b; / / i l n' exi st e pas d' opr at eur # en C++ 2 el se 3 c = a : b; / / i l n' exi st e pas d' opr at eur : en C++ 4
Qui plus est, parmi les oprateurs C++, cinq ne peuvent pas faire l'objet de fonctions oprateur :
Symbole Nature Commentaire si zeof Calcul de taille Cet oprateur donne dj le seul rsultat raisonnable (la taille en octet de son oprande), mme lorsqu'il est appliqu une classe ou une instance. Le redfinir ne donnerait certainement rien de bon. ?: if arithmtique : : Rsolution de porte . Slection d'un membre . * Drfrencement d'un pointeur sur membre Ces oprateurs touchent des mcanismes fondamentaux. Il est difficile d'envisager de les redfinir sans compromettre gravement la cohrence du langage.
Bien entendu, puisqu'il s'agit de confrer de nouvelles significations des symboles que le langage utilise dj, il faut que le contexte permette de lever l'ambigut. Deux cas peuvent se prsenter :
- Si la fonction oprateur est membre d'une classe, le type de l'objet au titre duquel elle est invoque la dsigne de faon parfaitement univoque.
- Si la fonction oprateur est globale (c'est--dire n'est pas membre d'une classe), il s'agit d'un cas de surcharge, et le type de ses arguments doit permettre de la distinguer de ses homonymes.
Nous pouvons donc conclure qu'une fonction oprateur est toujours lie un type dfini par le programmeur : soit parce qu'elle est membre d'une classe, soit parce qu'au moins un de ses paramtres relve d'un type numr ou d'une classe. J-L Pris - 05/06/07 C++ - Leon 11 Fonctions oprateur 3/12 Immuabilit de la prcdence des oprateurs et de leur arit Si la dfinition d'une fonction oprateur permet de spcifier comment cet oprateur s'applique de nouveaux types, il reste des caractristiques de l'oprateur que cette dfinition ne saurait remettre en cause. C'est en particulier le cas de la prcdence (ensemble de rgles qui dtermine dans quel ordre sont effectues les oprations lors de l'valuation d'une expression impliquant plusieurs oprateurs) et de l'arit (nombre d'arguments ncessaires l'application d'un oprateur). Ainsi, quel que soit le type des objets concerns,
x + y * z; / / l ' opr at eur * est pr i or i t ai r e par r appor t l ' opr at eur +
quivaut toujours x + ( y * z) et non ( x + y) * z, et il ne sera jamais possible d'crire
j = k &&; / / && est un opr at eur ncessi t ant deux opr andes ! Particularits des fonctions oprateur Par rapport une fonction "ordinaire", une fonction oprateur ne se distingue gure que de trois faons.
- La premire est assez anecdotique : le nom attribu la fonction lors de sa dclaration est compos du mot oper at or , suivi du symbole qui sera utilis pour appeler la fonction.
- La deuxime particularit des fonctions oprateur dcoule de la syntaxe particulire utilise pour les appeler : leurs paramtres ne peuvent pas tre dots de valeurs par dfaut.
- La dernire particularit mrite plus d'attention, car il s'agit de la faon dont la fonction a accs aux oprandes soumis l'oprateur : cette faon varie selon le nombre d'objets impliqus et selon si la fonction oprateur est globale ou membre d'une classe. Dans quels cas est-il judicieux de surcharger un oprateur ? Dans la plupart des cas, le recours une fonction oprateur ne relve que du souci d'augmenter le confort des programmeurs et d'assurer une lisibilit maximale au texte source. Ces objectifs ne seront atteints que dans la mesure o le symbole de l'oprateur surcharg rvle sans ambigut la nature de l'opration qu'il effectue.
S'il peut y avoir le moindre doute sur le sens qu'aurait l'application d'un oprateur l'un de vos types, n'utilisez pas un oprateur mais une fonction ordinaire portant un nom bien choisi.
Il existe toutefois des cas particuliers : l'utilisation de conteneurs (cf. Leon 8) exige parfois que l'oprateur de comparaison ==puisse tre appliqu deux instances de la classe concerne, et l'oprateur d'affectation doit parfois imprativement tre surcharg.
En effet, en l'absence d'une fonction oprateur le prenant explicitement en charge, l'oprateur d'affectation est automatiquement dfini comme une copie membre membre de l'oprande de droite vers l'oprande de gauche. Il s'agit l d'un traitement semblable celui opr par le constructeur par copie fourni automatiquement par le langage (cf. Leon 10). Si ce traitement convient parfaitement dans un trs grand nombre de cas, il s'avre parfois tout fait inacceptable, ce qui entrane la fois la ncessit de dfinir explicitement un constructeur par copie plus judicieux et celle de surcharger l'oprateur d'affectation. 2 - Surcharger les oprateurs monadiques Oprateur Exemples ! bool n = ! t r ue; - i nt a = - 2; + a = + 2; & i nt * pt r = & a; * * pt r = 4; ~ char c = ~ ' x' ; ++ ++ a; a ++; - - - - a; a - - ; Le langage C++ comporte neuf oprateurs qui s'appliquent un oprande unique. Dans la plupart des cas, la syntaxe exige que le symbole de l'oprateur prcde la dsignation de son oprande (cf. tableau ci- contre). Les oprateurs ++et - - admettent galement une syntaxe suffixe, qui doit tre prise en charge par une fonction spcifique.
L'oprateur - >, pour sa part, n'admet qu'un usage suffix et exige sa droite la prsence d'un identificateur de membre qui lui donne l'air d'avoir deux oprandes. - > t r uc- >f membr e( ) ; J-L Pris - 05/06/07 C++ - Leon 11 Fonctions oprateur 4/12 Les oprateurs prfixs La surcharge de ces oprateurs peut tre ralise de deux faons : soit par une fonction membre de la classe vise, soit par une fonction globale.
S'il s'agit d'une fonction membre, elle doit tre dpourvue d'argument, car elle sera invoque au titre de l'instance sur laquelle l'oprateur est appliqu. En d'autres termes, si une classe CTr uc possde, par exemple, une fonction membre surchargeant l'oprateur ! , alors l'instruction
! monTr uc; / / monTr uc est une i nst ance de l a cl asse CTr uc
est quivalente
monTr uc. oper at or ! ( ) ; / / appel expl i ci t e de l a f onct i on oper at or ! ( )
La dfinition d'une fonction membre surchargeant un oprateur ne prsente qu'une seule particularit : son nom complet comporte le mot oper at or prcdant le symbole de l'oprateur concern. Le type de la valeur ventuellement renvoye par la fonction, tout comme la logique des oprations qu'elle effectue, dpend bien entendu totalement de la smantique associe la classe en gnral, et l'oprateur concern en particulier.
CTr uc CTr uc: : oper at or ! ( ) const / / cet opr at eur est sans ef f et 1 { 2 CTr uc r esul t at = *t hi s; 3 / / modi f i cat i on de r esul t at pour cal cul er l e r sul t at de l ' appl i cat i on de ! r et ur n r esul t at ; 4 } 5
L'oprateur ! tant habituellement dnu d'effet, il est vraisemblable que l'objet auquel il est appliqu ne doit pas tre modifi (la fonction membre est donc constante et utilise une variable locale pour calculer le r esul t at ). De mme, l'oprateur ! produit gnralement un r esul t at du mme type que son oprande, ce qui explique que la fonction est de type CTr uc.
Si la fonction surchargeant l'oprateur est une fonction globale, elle doit tre munie d'un paramtre unique, qui correspond l'oprande sur lequel l'oprateur est appliqu. Si l'oprateur doit avoir un effet, ce paramtre sera de type "rfrence une instance de la classe vise", ce qui permettra la fonction de modifier l'oprande. Si l'oprateur ne fait que produire un rsultat, la fonction peut se contenter de recevoir une valeur ayant pour type la classe concerne, et les manipulations qu'elle effectuera sur son paramtre resteront sans effet sur l'objet utilis comme oprande. Un exemple d'une telle fonction globale pourrait tre :
CTr uc oper at or ! ( CTr uc par amet r e) / / cet opr at eur est sans ef f et 1 { 2 / / modi f i cat i ons de par amet r e pour cal cul er l e r sul t at de l ' appl i cat i on de ! 3 r et ur n par amet r e; / / r envoi e l a val eur obt enue 4 } 5
Bien que globale, la fonction oprateur va sans doute devoir accder des membres non publics de l'objet concern. La classe de cet objet doit donc la dclarer amie. Les oprateurs ++et - - suffixs Les oprateurs d'incrmentation et de dcrmentation doivent correspondre des fonctions oprateurs diffrentes selon s'ils sont placs avant ou aprs leur oprande. L'effet qu'ont ces oprateurs sur leur oprande est a priori le mme dans les deux cas, mais la valeur renvoye doit tre diffrente : il s'agit de la valeur de l'oprande avant que le traitement n'ait t effectu lorsque l'oprateur est suffix, et de la valeur de l'oprande aprs application du traitement lorsque l'oprateur est prfix.
Il serait trs fcheux qu'une fonction surchargeant l'oprateur ++ ou l'oprateur -- n'adhre pas ces conventions, qui sont profondment ancres dans l'esprit de tous les programmeurs C++.
Si la cration des fonctions oprateurs correspondant l'usage prfix ne diffre en rien du cas des autres oprateurs un seul argument, un problme se pose pour dfinir les fonctions correspondant l'usage suffix. Leur nom de la fonction (oper at or ++ou oper at or - - ) est en effet ncessairement le mme que pour la fonction correspondant l'usage prfix. Pour que les fonctions prenant en charge les usages prfix et suffix d'un mme oprateur puissent tre toutes deux dfinies, il faut donc que leurs signatures diffrent.
J-L Pris - 05/06/07 C++ - Leon 11 Fonctions oprateur 5/12 La liste des arguments ncessaires tant la mme dans les deux cas (aucun argument pour une fonction membre, un seul argument, du type concern, pour une fonction globale), et la constance de la fonction tant exclue par la nature de l'oprateur, les concepteurs du langage C++ ont dcid d'introduire un argument inutile (de type i nt ) pour singulariser la signature de la fonction oprateur devant tre invoque lorsque l'oprateur est utilis en position suffixe.
S'il s'agit de fonctions membre, les fonctions oprateur seront donc dfinies ainsi :
CTr uc & CTr uc: : oper at or ++( ) / / excut e l or sque ++ est PREFI XE 1 { 2 / / modi f i cat i on des var i abl es membr e pour r al i ser l ' ef f et associ ++ 3 r et ur n *t hi s; / / r envoi e l a val eur de l ' opr ande modi f i 4 } 5
const CTr uc CTr uc: : oper at or ++( i nt ) / / excut e l or sque ++ est POSTFI XE 1 { 2 const CTr uc r esul t at ( *t hi s) ; / / met l a val eur i ni t i al e de ct 3 ++ ( *t hi s) ; / / modi f i e l a val eur l ' ai de de l ' opr at eur pr f i x 4 r et ur n r esul t at ; / / r envoi e l a val eur i ni t i al e de l ' opr ande 5 } 6
Si les fonctions oprateur sont globales, elles prendront la forme suivante :
CTr uc & oper at or ++( CTr uc & oper ande) / / excut e l or sque ++ est PREFI XE 1 { 2 / / modi f i cat i on de oper ande pour r al i ser l ' ef f et associ ++ 3 r et ur n oper ande; / / r envoi e l a val eur de l ' opr ande modi f i 4 } 5
const CTr uc oper at or ++( CTr uc &oper ande, i nt ) / / excut e l or sque ++ est POSTFI XE 1 { 2 const CTr uc r esul t at ( oper ande) ; / / met l a val eur i ni t i al e de ct 3 ++ oper ande; / / modi f i e l a val eur l ' ai de de l ' opr at eur pr f i x 4 r et ur n r esul t at ; / / r envoi e l a val eur i ni t i al e de l ' opr ande 5 } 6
Il est prfrable que les fonctions prenant en charge l'usage prfix d'un oprateur ++ ou - - renvoient une rfrence l'objet sur lequel elles sont appliques, et non simplement la nouvelle valeur de celui-ci. Les expressions utilisant l'oprateur prfix deviennent ainsi des l val ue, ce qui autorise des choses comme ++unObj et . f onct i onMembr e( ) (incrmente l'objet, puis excute la f onct i onMembr e). On prfrera aussi que les oprateurs postfixs renvoient une constante, de faon interdire la compilation d'expressions pathologiques telles que unObj et ++++ (qui de toute faon, n'incrmenterait pas deux fois l'objet, puisque le second ++ serait appliqu la valeur que l'objet avait avant la premire incrmentation) L'oprateur - > L'oprateur - > s'interpose habituellement entre deux identificateurs et ne peut tre utilis comme un oprateur monadique habituel, que ce soit en position prfixe ou suffixe :
obj et - >l eMembr e; / / OK 1 - >l eMembr e; / / ?? a ne veut r i en di r e ! 2 obj et - >; / / ?? a ne veut r i en di r e ! 3
Il n'en demeure pas moins que, si l'on appelle explicitement l'oprateur - >, la ligne 1 devient
( obj et . oper at or - > ( ) ) - >l eMembr e; / / qui vaut obj et - >l eMembr e;
L'examen de cette expression conduit deux conclusions :
- L'oprateur - >est bien monadique (comme nous le verrons bientt, un second oprande lui aurait t transmis par le passage d'un paramtre dont l'absence est ici vidente).
- La fonction oprateur prenant en charge - >doit ncessairement renvoyer l'adresse d'un objet ayant un membre nomm l eMembr e (puisque l'oprateur standard de slection indirecte est appliqu sur le rsultat produit par l'appel explicite de notre oprateur - >).
J-L Pris - 05/06/07 C++ - Leon 11 Fonctions oprateur 6/12 La surcharge de l'oprateur - >ne peut tre ralise que par une fonction membre et permet de crer des classes dont les instances se comportent comme des pointeurs. 3 - Surcharger les oprateurs dyadiques Le langage C++ permet de surcharger trente deux oprateurs produisant un rsultat partir de deux oprandes placs de part et d'autre de leur symbole 1 . Trente de ces oprateurs peuvent faire l'objet soit d'une fonction globale, soit d'une fonction membre d'une classe, alors que les oprateurs =et [ ] ne peuvent tre pris en charge que par des fonctions membre.
Un oprateur admettant deux oprandes de types diffrents jouant des rles symtriques doit gnralement tre surcharg par deux fonctions : l'une prendra en charge les expressions adoptant l'ordre t ypeA # t ypeB, et l'autre traitera les cas t ypeB # t ypeA. Si cette prcaution n'est pas prise, les utilisateurs devront se faire l'ide qu'ils peuvent par exemple crire monTr uc + 4; mais pas 4 + monTr uc; Fonction oprateur globale Les oprateurs dyadiques susceptibles d'tre surchargs par une fonction globale sont :
Remarquez que les symboles +, - , * et & correspondent chacun deux oprateurs : un oprateur monadique et un oprateur dyadique. C'est donc le contexte (c'est dire, ici, la prsence d'un ou de deux oprandes) qui dtermine la signification de chacune des occurrences de l'un de ces symboles. Les caractres * et & sont aussi utiliss pour dclarer respectivement des pointeurs et des rfrences. Ils ne dsignent aucun oprateur lorsqu'ils apparaissent dans ce contexte.
Tous ces oprateurs utilisent une notation infixe : l'un des oprandes est gauche de l'oprateur, alors que l'autre est droite. Cette rgularit permet d'exprimer simplement la correspondance entre les oprandes et les arguments d'une fonction globale surchargeant un oprateur dyadique : si #reprsente l'un des 30 symboles mentionns ci-dessus
oper andeGauche # oper andeDr oi t ; / / not at i on i nf i xe
est quivalent l'appel explicite de la fonction ralis par
oper at or #( oper andeGauche, oper andeDr oi t ) ;
Comme nous l'avons signal dans la Leon 7, il est souvent intressant de rendre les classes capables de se dcrire elles-mmes sous formes textuelles. Il devient alors possible d'envoyer dans un fichier le contenu de toutes les variables membres d'un objet, en une seule opration :
/ / f i chi er est un QText St r eampr t r ecevoi r des donnes f i chi er << unTr uc; / / unTr uc est une i nst ance de l a cl asse CTr uc
Il faut pour cela dfinir une fonction prenant en charge l'oprateur d'insertion lorsque son oprande de droite est de type CTr uc :
QText St r eam& oper at or << ( QText St r eam&out , CTr uc l eTr uc) 1 { 2 / / i l suf f i t i ci d' envoyer chacun des membr es dans l e f l ux out l ' ai de / / d' i nst r uct i ons du t ype : out << l eTr uc. l eMembr e << " \ n" ; 3 / / et c r et ur n out ; 4 } 5
Bien entendu, cette fonction globale ne sera autorise accder aux membres de son second paramtre que si elle bnficie d'une relation d'amiti avec la classe CTr uc.
1 Dans le cas de l'oprateur [ ] , l'oprande de droite prend place entre les deux crochets et, en toute rigueur, les oprandes ne sont donc pas parfaitement "de part et d'autre" du symbole de l'oprateur. J-L Pris - 05/06/07 C++ - Leon 11 Fonctions oprateur 7/12 Remarquez que la fonction oper at or <<ne se contente pas d'insrer du texte dans le flux qui lui est dsign par son premier paramtre, mais qu'elle prend aussi la peine de renvoyer la rfrence ce flux qui lui a t transmise. En d'autres termes, la fonction ne se contente pas de raliser l'effet souhait, elle confre de plus une valeur de type "rfrence un flux" l'expression qui l'a appele. Pourquoi est-il intressant qu'une expression telle que f i chi er << unTr uc; soit de type "rfrence un flux" ? Parce que, de cette faon, elle dsigne un flux, ce qui permet d'utiliser l'expression en question comme oprande de gauche d'un autre oprateur d'insertion : f i chi er << unTr uc << " Fi n des val eur s cont enues dans unTr uc\ n" Fonction oprateur membre d'une classe Lorsque la fonction oprateur est membre d'une classe, la notation infixe usuelle
oper andeGauche # oper andeDr oi t ; / / not at i on i nf i xe
est quivalente l'appel explicite de la fonction ralis par
oper andeGauche. oper at or #( oper andeDr oi t ) ;
Cette quivalence syntaxique rvle immdiatement une contrainte importante :
Il n'est possible de surcharger un oprateur dyadique l'aide d'une fonction membre d'une classe que lorsque l'oprande de gauche est une instance de cette classe.
En contrepartie, outre les trente autres oprateurs dyadiques surchargeables,
Les oprateurs [ ] et =peuvent tre surchargs par des fonctions membre.
Les fonctions dfinissant l'oprateur d'affectation renvoient habituellement une rfrence l'objet au titre duquel elles sont excutes, ce qui permet de chaner les affectations : t r ucUn = t r ucDeux = t r ucTr oi s;
Si la classe CTr uc comporte deux variables membres nommes m_a et m_b, il peut tre intressant de dfinir un oprateur de test d'galit entre instances qui renvoie t r ue si et seulement si les deux membres ont des valeurs identiques dans ses deux oprandes :
bool CTr uc: : oper at or == ( CTr uc unAut r e) 1 { 2 r et ur n ( m_a == unAut r e. m_a && m_b == unAut r e. m_b) ; 3 } 4
En tant que fonction membre de CTr uc, notre fonction oprateur dispose de privilges lui autorisant l'accs aux membres de son paramtre (qui est galement de classe CTr uc), mme si ceux-ci sont protgs (ce qui est souhaitable).
Une fois cet oprateur dfini, il devient possible de comparer deux instances de CTr uc :
i f ( pr emi er Tr uc == secondTr uc) / / t r ai t er l e cas d' gal i t
Rappel : aucune version par dfaut de l'oprateur de comparaison n'est fournie par le langage. Si des comparaisons entre instances sont ncessaires, il faut crire une fonction les effectuant. Il n'est pas indispensable de surcharger l'oprateur ==, puisqu'une fonction membre nomme est Egal A( ) et utilisant un argument du type concern permettra d'obtenir le mme effet : i f ( pr emi er Tr uc. est Egal A( secondTr uc) ) / / t r ai t er l e cas d' gal i t La lisibilit de cette version est un peu moindre et, comme nous le savons, certaines fonctionnalits des conteneurs seront rendues disponibles par une fonction oper at or ==( ) , mais pas par une fonction est Egal A( ) . 4 - Un cas particulier : l'oprateur "parenthses" Les fonctions oper at or ( ) prsentent deux caractristiques remarquables, ayant trait l'une au nombre de leurs paramtres et l'autre leur ventuelle excution la suite d'un appel implicite. Ces caractristiques sont prfrentiellement exploites pour rpondre deux types de J-L Pris - 05/06/07 C++ - Leon 11 Fonctions oprateur 8/12 proccupations bien diffrents, et la seconde est obtenue au prix d'une syntaxe tout fait particulire. Une rgle fondamentale s'applique cependant dans tous les cas :
L'oprateur ( ) ne peut tre dfini que par une fonction membre. Fonctionodes Les fonctions surchargeant l'oprateur "parenthses" peuvent recevoir plusieurs paramtres. En effet, bien que cet oprateur soit dyadique, son oprande de droite est une liste, ce qui permet de fournir des valeurs permettant d'initialiser un nombre quelconque de paramtres. Si la classe CTr uc possde une fonction oprateur du genre
voi d CTr uc: : oper at or ( ) ( i nt a, i nt b, i nt c, doubl e x) {}
il devient possible d'crire des choses comme
CTr uc unTr uc; 1 i nt a = 7; 2 unTr uc( 2, a, a+2, 2. 4) ; 3
Comme le montre le fragment de code ci-dessus, l'utilisation normale de l'oprateur ( ) ressemble l'appel d'une fonction membre dpourvue de nom : le nom de l'instance concerne est directement suivi du couple de parenthses encadrant les valeurs transmises. Il reste bien entendu possible d'appeler explicitement la fonction en utilisant son nom :
unTr uc. oper at or ( ) ( 2, a, a+2, 2. 4) ;
Si l'oprateur "parenthses" est parfois appel "oprateur d'appel de fonction", on voit donc bien ce que cette expression a d'abusif : surcharger l'oprateur "parenthses" revient simplement simuler l'existence d'une ou plusieurs fonction(s) membre anonyme(s), et ceci n'a aucun effet sur le processus d'appel des autres fonctions. Une instance d'une classe surchargeant ainsi l'oprateur parenthses se comporte donc comme une fonction, et les objets de ce genre sont souvent appels "foncteurs", ou mme "fonctionodes". Oprateurs de transtypage Il arrive trs frquemment que tout ou partie de l'information contenue dans une variable d'un certain type doive tre transmise une fonction disposant d'un paramtre d'un autre type. Lorsque le type attendu est une classe, on peut rendre possible la cration automatique d'une valeur adquate en dotant cette classe d'un constructeur de transtypage (cf. Leon 10).
L'oprateur ( ) permet d'obtenir un effet analogue mais, comme il est dfini dans la classe source, il permet de traiter les cas o il n'est pas envisageable d'ajouter un constructeur de transtypage au type attendu (parce que ce n'est pas une classe, ou parce que c'est une classe faisant partie d'une librairie que l'on ne peut pas modifier, par exemple).
Imaginons, par exemple, une classe quelconque :
cl ass CDat a 1 { 2 publ i c: 3 / / i nt er f ace de l a cl asse pr ot ect ed: 4 i nt a; 5 doubl e x; 6 }; 7
Si nous souhaitons disposer facilement d'une reprsentation textuelle de l'tat d'une instance de cette classe, nous ne pouvons pas crer un constructeur de QSt r i ng prenant pour argument une valeur de type CDat a (sauf, bien sr, si nous sommes prts recompiler la librairie Qt et vivre avec notre propre version de la classe QSt r i ng). L'instruction
/ / i l exi st e un obj et de t ype CDat a nomm uneDat a QSt r i ng t ext e( uneDat a) ;
peut nanmoins tre rendue acceptable en ajoutant un oprateur de transtypage CDat a.
J-L Pris - 05/06/07 C++ - Leon 11 Fonctions oprateur 9/12 La dclaration d'une telle fonction prend une forme inhabituelle, car le type de la valeur renvoye (QSt r i ng, dans notre exemple) abandonne sa place en tte de dclaration pour venir s'insrer entre le mot oper at or et les parenthses compltant le nom de la fonction :
cl ass CDat a 1 { 2 publ i c: 3 / / i nt er f ace de l a cl asse oper at or QSt r i ng ( ) ; / / comme si c' t ai t une f onct i on nomme QSt r i ng( ) 4 pr ot ect ed: 5 i nt a; 6 doubl e x; 7 }; 8
La dfinition de l'oprateur n'a, pour sa part, rien de bien original :
CDat a: : oper at or QSt r i ng( ) 1 { 2 QSt r i ng t ext e = " a: %1, x: %2" ; 3 r et ur n t ext e. ar g( a) . ar g( x) ; 4 } 5
Cette fonction peut tre invoque de diffrentes faons :
/ / i l exi st e un obj et de t ype CDat a nomm uneDat a QString texte(uneData); / / appel i mpl i ci t e de CDat a: : oper at or QSt r i ng( ) 1 t ext e = uneDat a. oper at or QSt r i ng ( ) ; / / appel expl i ci t e 2 t ext e = ( QSt r i ng) uneDat a; / / t r anst ypage expl i ci t e ( synt axe hr i t e du C) 3 t ext e = ge expl i ci t e ( synt axe C++ pr i mi t i ve) QSt r i ng ( uneDat a) ; / / t r anst ypa 4 t ext e = static_cast<QString> (uneData); / / t r anst ypage expl i ci t e ( C++ act uel ) 5
Si le contexte d'utilisation n'est pas propice au transtypage implicite, c'est incontestablement la forme 5 qui doit tre prfre.
Les formes 3 et 4 ne sont destines qu' assurer la compatibilit des textes sources antrieurs la norme ISO du C++. La forme 2 est admissible, mais trs inhabituelle (comme tous les appels explicites de fonctions oprateur).
La mme classe peut se trouver munie de plusieurs oprateurs de transtypage permettant d'obtenir des valeurs de divers types. Il arrive alors parfois qu'il soit ncessaire de choisir explicitement l'un des transtypages disponibles.
Supposons, par exemple, qu'une classe C dispose d'oprateurs de transtypage vers les types T1 et T2. Supposons galement qu'il existe deux fonctions homonymes dont les signatures ne diffrent que parce que l'une attend une valeur de type T1 et l'autre une valeur de type T2. Si l'on tente de passer une valeur de type C l'une de ces fonctions, le compilateur se trouve face un dilemme : il peut soit gnrer une valeur de type T1 et appeler la premire fonction, soit gnrer une valeur de type T2 et appeler la seconde. Cette ambigut ne peut tre rsolue qu'en invoquant explicitement l'un des oprateurs c: : oper at or ( ) .
La disponibilit de plusieurs oprateurs de transtypages diffrents dans une mme classe cre une situation tout fait exceptionnelle en C++, puisqu'il s'agit de fonctions homonymes ayant la mme signature. C'est le seul cas o le langage admet des fonctions diffrentes ne se distinguant que par le type de la valeur qu'elles renvoient.
C'est sans doute pour souligner ce privilge exceptionnel confr au type de la fonction que la syntaxe lui fait prendre une place inhabituelle dans la dclaration de celle-ci. 5 - Oprateurs sur enum: un exemple La possibilit de surcharger les oprateurs n'est pas rserve aux cas o ceux-ci sont appliqus des instances de classes. Il est tout fait possible de crer des fonctions oprateur globales dont l'un des paramtres est d'un type numr, ce qui permet d'accrotre considrablement l'intrt de ce genre d'objets.
J-L Pris - 05/06/07 C++ - Leon 11 Fonctions oprateur 10/12 Pour bien comprendre l'exemple qui va suivre, il est ncessaire de savoir qu'il existe une correspondance entre les valeurs numres et les valeurs entires. Ainsi, si le type EJ our est dfini par
la valeur LUNDI correspond 0, MARDI 1, et ainsi de suite jusqu' DI MANCHE, qui correspond 6. Le langage opre automatiquement, en cas de besoin, la conversion du type numr vers le type i nt , comme l'illustre le fait que
i nt x = J EUDI ;
initialise la variable x avec la valeur 3. La conversion inverse n'est en revanche pas effectue automatiquement, et l'instruction
EJ our unJ our = 3; / / ERREUR !
ne provoquera pas l'initialisation de la variable unJ our avec la valeur J EUDI , mais une pure et simple erreur de compilation. Conversion d'une valeur entire en valeur de type EJ our La premire chose faire est donc de se doter d'une fonction permettant de convertir un entier en un EJ our de valeur quivalente. Au passage, cette fonction peut en profiter pour ramener dans la plage de valeurs admissible les valeurs dpassant celle correspondant DI MANCHE. Les ventuelles valeurs ngatives peuvent aussi tre ramenes dans la plage admissible, au moyen d'une simple addition. Un transtypage explicite permet alors de convertir la valeur entire (dont nous sommes maintenant certains qu'elle est positive et strictement infrieure 7) en une valeur de type EJ our .
EJ our j our ( i nt val ) / / f onct i on de t r anst ypage d' i nt ver s EJ our 1 { 2 r et ur n st at i c_cast <EJ our > ( ( ( val %=7) < 0) ? val +7 : val ) ; 3 } 4
Le type EJ our n'tant pas une classe, il ne peut pas disposer de fonctions membre, ce qui nous empche de le doter d'un vritable oprateur de transtypage. Cette restriction ne nous prive toutefois que de la possibilit de transtypage implicite, l'appel explicite de la fonction j our ( ) assurant exactement le traitement requis. Reprsentation crite de la valeur d'un EJ our Le transtypage automatique d'une valeur de type EJ our en la valeur entire quivalente ne permet pas toujours d'obtenir l'effet souhait. Un problme frquemment rencontr est celui de l'insertion d'un EJ our dans un flux : l'apparition de valeurs numriques dans celui-ci ne correspond sans doute pas l'attente du programmeur qui utilise des objets de type EJ our . Ce problme peut tre rsolu en surchargeant l'oprateur d'insertion dans les flux, de faon ce que ceux-ci reoivent une chane de caractres plus vocatrice de la signification de la valeur.
QText St r eam& oper at or << ( QText St r eam& dest i nat i on, EJ our unJ our ) 1 { 2 swi t ch ( unJ our ) 3 { 4 case LUNDI : dest i nat i on << " Lundi " ; br eak; 5 case MARDI : dest i nat i on << " Mar di " ; br eak; 6 case MERCREDI : dest i nat i on << " Mer cr edi " ; br eak; 7 case J EUDI : dest i nat i on << " J eudi " ; br eak; 8 case VENDREDI : dest i nat i on out << " Vendr edi " ; br eak; 9 case SAMEDI : dest i nat i on << " Samedi " ; br eak; 10 case DI MANCHE : dest i nat i on << " Di manche" ; br eak; 11 } 12 r et ur n dest i nat i on; 13 } 14 Fonctions oper at or ++( ) J-L Pris - 05/06/07 C++ - Leon 11 Fonctions oprateur 11/12 Passer d'un jour l'autre est une opration que l'on peut incontestablement qualifier de quotidienne. L'expression de ce phnomne dans un programme sera simplifie si l'oprateur d'incrmentation est capable d'agir sur les EJ our . La dfinition des fonctions oprateur rendant ceci possible est simplifie par la disponibilit de la fonction j our ( ) .
EJ our & oper at or ++ ( EJ our &unJ our ) / / ++ pr f i x 1 { 2 unJ our = j our ( unJ our + 1) ; 3 r et ur n unJ our ; 4 } 5
EJ our oper at or ++ ( EJ our &unJ our , i nt ) / / post f i xed ++ 1 { 2 EJ our avant = unJ our ; 3 ++unJ our ; / / ut i l i sat i on de l ' opr at eur pr f i x df i ni ci - dessus ! 4 r et ur n avant ; 5 } 6
La conversion automatique est mise contribution : pour valuer le rsultat de l'addition, la valeur de unJ our doit tre convertie en une valeur entire (puisque nous n'avons pas dfini d'oprateur +prenant en charge les EJ our ). Ce rsultat est ensuite reconverti en une valeur de type EJ our , qui peut ensuite tre affecte l'objet cible. L'effet de l'oprateur tant obtenu, il reste fournir son rsultat, ce qui est simplement ralis en renvoyant la rfrence reue comme paramtre. Utilisation du type EJ our Une fois ces quelques fonctions dfinies, il est possible d'crire du code ressemblant ceci :
/ / f i chi er est un QText St r eampr t r ecevoi r des donnes EJ our auj our dhui = MERCREDI ; 1 i nt n; 2 f or ( n = 0 ; n < 9 ; n++) 3 f i chi er << auj our dhui ++ << " , " ; 4
L'excution d'une telle boucle insrerait dans f i chi er le texte suivant :
Mer cr edi , J eudi , Vendr edi , Samedi , Di manche, Lundi , Mar di , Mer cr edi , J eudi
Selon les besoins, on peut bien entendu envisager de surcharger d'autres oprateurs (- - , +=, - =, +, - et l'extraction depuis un flux sont les premiers qui viennent l'esprit), de faon augmenter encore les possibilits d'utilisation des variables de type EJ our . 6 - Bon, c'est gentil tout a, mais a fait dj 9 pages. Qu'est-ce que je dois vraiment en retenir ? 1) Les fonctions oprateur permettent de dfinir la faon dont certains oprateurs s'appliquent aux types dfinis par l'utilisateur, qu'il s'agisse de classes ou de types numrs.
2) Une fonction surchargeant un oprateur est soit globale soit membre de la classe dont l'oprande de gauche est une instance.
3) Du point de vue de la mise au point du code qui la dfinit, une fonction oprateur n'est en rien diffrente d'une fonction "ordinaire" qui ferait le mme travail.
4) Les oprateurs =, < et == sont les seuls dont la surcharge est parfois rellement indispensable.
5) La disponibilit d'oprateurs surchargs peut accrotre considrablement l'agrment d'utilisation des types dfinis par l'utilisateur, condition que l'usage fait des oprateurs reste suffisamment proche de la signification usuelle de ceux-ci.
6) L'utilisation d'oprateurs surchargs ralisant des traitements qui s'loignent trop du sens habituel du symbole utilis peut rendre les programmes parfaitement incomprhensibles.
J-L Pris - 05/06/07 C++ - Leon 11 Fonctions oprateur 12/12 7) Doter une classe d'un oprateur ( ) peut ouvrir la possibilit de transtypages automatiques gnrant une valeur d'un type prdfini partir de la valeur d'une instance de cette classe.