Exercices en langage C++
Les conversions de type définies par l’utilisateur
Rappels C++ vous permet de définir des conversions d’un type classe vers un autre type classe ou un type de base. On parle de conversions définies par l’utilisateur (en abrégé : C.D.U.). Ces conversions peuvent alors éventuellement être mises en œuvre de façon implicite par le compilateur, afin de donner une signification à un appel de fonction ou à un opérateur (sans conversion, l’appel ou l’opérateur serait illégal). On retrouve ainsi des possibilités comparables à celles qui nous sont offertes par le C en matière de conversions implicites. Deux sortes de fonctions (obligatoirement des fonctions membre) permettent de définir des C.D.U. : ■ les constructeurs à un argument (quel que soit le type de cet argument) réalisent une conversion du type de cet argument dans le type de sa classe ; on peut cependant utiliser le mot-clé explicit devant la déclaration du constructeur pour en interdire l’utilisation dans une conversion implicite ; Les conversions de type définies par l’utilisateur ■ les opérateurs de cast ; dans une classe A, on définira un opérateur de conversion d’un type x (quelconque, c’est-à-dire aussi bien un type de base qu’un autre type classe) en introduisant la fonction membre de prototype : operator x () ; Notez que le type de la valeur de retour (obligatoirement défini par son nom) ne doit pas figurer dans l’en-tête ou le prototype d’une telle fonction. Les règles d’utilisation des C.D.U. rejoignent celles concernant le choix d’une fonction surdéfinie : ■ Les C.D.U. ne sont mises en œuvre que si cela est nécessaire. ■ Une seule C.D.U. peut intervenir dans une chaîne de conversions (d’un argument d’une fonction ou d’un opérande d’un opérateur). ■ Il ne doit pas y avoir d’ambiguïté, c’est-à-dire plusieurs chaînes de conversions conduisant au même type, pour un argument ou un opérande donné. N. B. Aucune conversion n’est réalisable sur un argument effectif en cas de transmission par référence, sauf si l’argument muet correspondant est déclaré avec l’attribut const ; dans ce dernier cas, on retrouve les mêmes possibilités de conversion qu’en cas de transmission par valeur.
Exercice
Énoncé
Soit la classe point suivante : class point { int x, y ; public : point (int abs=0, int ord=0) { x = abs ; y = ord ; } // ….. } ; a. La munir d’un opérateur de cast permettant de convertir un point en un entier (correspondant à son abscisse). b. Soient alors ces déclarations : point p ; int n ; void fct (int) ; exos_c++.book Page 184 Jeudi, 5. juillet 2007 11:10 11 © Éditions Eyrolles 185 chapitre n° 12 Les conversions de type définies par l’utilisateur a. Il suffit de définir une fonction membre, de nom operator int, sans argument et renvoyant la valeur de l’abscisse du point l’ayant appelée. Rappelons que le type de la valeur de retour (que C++ déduit du nom de la fonction – ici int) ne doit pas figurer dans l’en-tête ou le prototype. Voici ce que pourraient être la déclaration et la définition de cette fonction, ici réunies en une seule déclaration d’une fonction en ligne : operator int () { return x ; } b. L’instruction 1 est traduite par le compilateur en une conversion de p en int (par appel de operator int), suivie d’une affectation du résultat à n. Notez bien qu’il n’y a pas d’appel d’un quelconque opérateur d’affection de la classe point, ni d’un constructeur par recopie (car le seul argument transmis à la fonction operator int est l’argument implicite this). L’instruction 2 est traduite par le compilateur en une conversion de p en int (par appel de operator int), suivie d’un appel de la fonction fct, à laquelle on transmet le résultat de cette conversion. Notez qu’il n’y pas, là non plus, d’appel d’un constructeur par recopie, ni pour fct (puisqu’elle reçoit un argument d’un type de base), ni pour operator int (pour la même raison que précédemment). Exercice 96 Que font ces instructions : n = p ; // instruction 1 fct (p) ; // instruction 2
Exercice
Énoncé Quels résultats fournira le programme suivant : #include using namespace std ; class point { int x, y ; public : point (int abs, int ord) // constructeur 2 arguments { x = abs ; y = ord ; } operator int() // « cast » point –> int { cout << « ** appel int() pour le point » << x << » » << y << « \n » ; return x ; } } ; void fct (double v) { cout << « $$ appel fct avec argument : » << v << « \n » ; } main() { point a(1,4) ; int n1 ; double z1, z2 ; n1 = a + 1.75 ; // instruction 1 cout << « n1 = » << n1 << « \n » ; z1 = a + 1.75 ; // instruction 2 cout << « z1 = » << z1 << « \n » ; z2 = a ; // instruction 3 cout << « z2 = » << z2 << « \n » ; fct (a) ; // instruction 4 } exos_c++.book Page 187 Jeudi, 5. juillet 2007 11:10 11 Exercices en langage C++ 188 © Éditions Eyrolles Pour évaluer l’expression a + 1.75 de l’instruction 1, le compilateur met en place une chaîne de conversions de a en double (point –> int suivie de int –> double) de manière à aboutir à l’addition de deux valeurs de type double ; le résultat, de type double, est ensuite converti pour être affecté à n1 (conversion forcée par l’affectation, comme d’habitude en C). Notez bien qu’il n’est pas question pour le compilateur de prévoir la conversion en int de la valeur 1.75 (de façon à se ramener à l’addition de deux int, après conversion de a en int) car il s’agit là d’une « conversion dégradante » qui n’est jamais mise en œuvre de manière implicite dans un calcul d’expression. Il n’y a donc pas d’autre choix possible (notez que s’il y en avait effectivement un autre, il ne s’agirait pas pour autant d’une situation d’ambiguïté dans la mesure où le compilateur appliquerait alors les règles habituelles de choix d’une fonction surdéfinie). L’instruction 2 correspond à un raisonnement similaire, avec cette seule différence que le résultat de l’addition (de type double) peut être affecté à z1 sans conversion. Enfin les instructions 3 et 4 entraînent une conversion de point en double, par une suite de conversions point –> int et int –> double. Voici le résultat de l’exécution du programme : ** appel int() pour le point 1 4 n1 = 2 ** appel int() pour le point 1 4 z1 = 2.75 ** appel int() pour le point 1 4 z2 = 1 ** appel int() pour le point 1 4 $$ appel fct avec argument : 1.