Voici le chapitre sur la surcharge des opérateurs en C++.
Si vous avez des remarques ou suggestions afin de le parfaire.
N’hésitez pas à me contacter via mon email:
pr.azizdarouichi@gmail.com.
Bonne lecture
2. 2
Contexte
Exemples d’appels d’opérateurs
Surcharge (ou surdéfinition) de fonction
Surcharge interne et surcharge externe
Fonctions amies
Surcharge externe et friendship
Les opérateurs de comparaison
Lien entre opérateurs
Surcharge interne ou Surcharge externe?
Surcharges de quelques opérateurs usuels
Pourquoi const en type de retour ?
Pourquoi operator<< retourne-t-il un ostream& ?
Quel type de retour pour operator+= ?
Surcharge de l’opérateur d’affectation
Q & A
Contexte
Exemples d’appels d’opérateurs
Surcharge (ou surdéfinition) de fonction
Surcharge interne et surcharge externe
Fonctions amies
Surcharge externe et friendship
Les opérateurs de comparaison
Lien entre opérateurs
Surcharge interne ou Surcharge externe?
Surcharges de quelques opérateurs usuels
Pourquoi const en type de retour ?
Pourquoi operator<< retourne-t-il un ostream& ?
Quel type de retour pour operator+= ?
Surcharge de l’opérateur d’affectation
Q & A
Chapitre 6: Surcharge des opérateurs
2
4. Exemple (2/2):
Output:
Nombre complexe: 4.5, 6.5
Nombre complexe: 9, 12.3
int main(){
Complexe z1(1.5, 2.5), z2(3, 4), z3(4.5, 5.8), z4, z5;
z4 = z1.add(z2);
z4.afficher();
z5 = z1.add(z2.add(z3));
z5.afficher();
}
44
Contexte
5. Il est quand même plus naturel d’écrire :
z4 = z1 + z2 ; //que z4 = z1.add(z2);
z5 = z1 + z2 + z3; //que z3 = z1.add(z2.add(z3));
De même, on préfèrera unifier l’affichage :
cout << "z3 = " << z3 << endl;
plutôt que d’écrire :
cout << "z3 = ";
z3.afficher();
cout << endl;
55
Contexte
6. a + b operator+(a, b) ou a.operator+(b)
b + a operator+(b, a) ou b.operator+(a)
-a operator-(a) ou a.operator-()
cout << a operator<<(cout, a) ou cout.operator<<(a)
a = b a.operator=(b)
a += b operator+=(a, b) ou a.operator+=(b)
++a operator++(a) ou a.operator++()
not a operator not(a) ou a.operator not()
ou operator!(a) ou a.operator!()
66
Exemples d’appels d’opérateurs
7. Surcharge (ou surdéfinition) de fonction
7
Rappel:
Deux fonctions ayant le même nom mais pas les mêmes paramètres.
Exemple :
int max(int, int);
double max(double, double);
De la même façon, on va pouvoir écrire plusieurs fonctions pour les
opérateurs;
Exemple :
Complexe operator+(Complexe, Complexe);
Matrice operator+(Matrice, Matrice);
7
8. Surcharge interne et surcharge externe
8
La surcharge des opérateurs peut être réalisée
soit à l’extérieur (par des fonctions),
Complexe operator+(Complexe, Complexe);
soit à l’intérieur (par des méthodes)
class Complexe{
public:
Complexe operator+(Complexe) const;
};
de la classe à laquelle ils s’appliquent.
8
9. Surcharge externe
9
Vu que les attributs sont privés et donc inaccessibles depuis l'extérieur
de la classe. Il existe trois solutions pour la surcharge externe des
opérateurs:
via des accesseurs,
via une autre méthode créée dans la classe,
via le concept d’amitié.
9
13. Les opérateurs de flux
Exemple:
cout << z1; operator<<(cout, z1);
Le prototype est:
ostream& operator<<(ostream&, Complexe const&);
Le paramètre ostream est passé par référence,
On passe le deuxième paramètre par référence constante, vu que l’on ne fait qu'afficher
le contenu de ce paramètre et rien d’autre.
Le type de retour est une référence sur l’objet ostream, ce qui permet de faire une chaîne :
cout << z1 << z2 << z3;
1313
Surcharge externe
19. Une fonction membre a accès aux membres publics et privés de la
classe.
Une classe avait généralement des membres privés (principe
d’encapsulation), et que ceux-ci n’étaient pas accessibles par des
fonctions non-membres.
Dans certains cas, cependant, on souhaite pouvoir utiliser une
fonction qui puisse accéder aux membres d’une classe, sans toutefois
nécessairement disposer d’une instance de cette classe par laquelle
l’appeler.
Fonctions amies
19
20. Une fonction amie d’une classe est une fonction qui, sans être
membre de cette classe, a le droit d’accéder à tous ses membres.
Une fonction est l’amie (friend) d’une classe lorsqu’elle est autorisée
à adresser directement les membres privés de cette classe.
L'amitié est déclarée en utilisant le mot-clé réservé: friend
Il importe peu de déclarer une fonction amie dans la partie public ou
private d'une classe donnée. Dans les deux cas, la fonction sera vue
comme étant une fonction public.
Fonctions amies
20
21. Exemple:
La fonction afficher() est une fonction amie de la classe Tableau.
Déclarer la fonction afficher() private ou public importe peu.
Fonctions amies
class Tableau{
private:
int nbr; // la taille du tableau tab
double* tab; // pointeur vers un tableau de double
friend void afficher(const Tableau &);
public:
Tableau(int nbrElements);
...
};
21
void afficher(const Tableau &t){
cout << "[";
for (int i(0); i < t.nbr; i++)
cout << " " << t.tab[i];
cout << "]" ;
}
22. Friend
Parfois, il peut être nécessaire d’autoriser les opérateurs externes
d’accéder à certains éléments private.
Dans ce cas, ajoutez, dans la définition de la classe, leur prototype
précédé du mot clé friend :
friend const Complexe operator*(double, Complexe const&);
friend ostream& operator<<(ostream&, Complexe const&);
Le mot clé friend signifie que ces fonctions, bien que ne faisant pas
partie de la classe, peuvent avoir accès aux attributs et méthodes
private de la classe.
Les définitions restent hors de la classe (et sans le mot clé friend).
2222
Surcharge externe et friendship
23. Exemple (1/2):
class Complexe{
friend ostream& operator<<(ostream& sortie, Complexe const& z); //Notez la présence
//de friend
private:
double real;
double img;
public:
Complexe(double a, double b): real(a), img(b){}
Complexe(): Complexe(0.0, 0.0) {}
}; //Fin de la classe
ostream& operator<<(ostream& sortie, Complexe const& z){
sortie << "(" << z.real << ", " << z.img << ")";
return sortie;
}
2323
Surcharge externe et friendship
25. 2525
Surcharge externe et friendship
L’opérateur de flux >>
On va surcharger l’opérateur >> pour la classe Complexe.
Voici le prototype de cet opérateur:
istream& operator>>(istream&, Complexe & );
Le paramètre istream est passé par référence,
Le deuxième paramètre est passé par référence, il représente le nombre
complexe à définir.
Le type de retour est une référence sur istream, ce qui permet de faire une
chaîne :
cin >> z1 >> z2;
27. 2727
Surcharge de l’opérateur de flux >>
Exemple (2/2):
int main(){
Complexe z;
cin >> z;
cout << z;
return 0;
}
Output:
Partie réelle ?
3.5
Partie imaginaire ?
4.5
(3.5, 4.5)
28. Les opérateurs de comparaison
On va surcharger les opérateurs de comparaison (==, !=, <, >=,...)
Voici le prototype de ces opérateurs:
bool operatorOp(NomDeLaClasse const&obj1, NomDeLaClasse const& obj2);
2828
Surcharge externe
29. Operateur ==
Commençons par l'opérateur de test d'égalité ==.
Pour être capables d'utiliser le symbole « == » entre deux objets, vous devez créer
une fonction ayant précisément pour nom operator==et dotée du prototype :
bool operator==(Complexe const& a, Complexe const& b);
Mode d'utilisation
if (z1 == z2){
std::cout << "Les deux nombres complexes sont égaux !" << std::endl;
}
2929
Les opérateurs de comparaison
30. L'implémentation de l'opérateur==
1ère solution avec friend
Voici la définition de la fonction operator==:
bool operator== (Complexe const& a, Complexe const& b){
if (a.real == b.real && a.img == b. img)
return true;
else
return false;
}
Ou directement
bool operator== (Complexe const& a, Complexe const& b){
return (a.real == b.real && a.img == b. img);
}
3030
Les opérateurs de comparaison
31. L'implémentation de l'opérateur==
1ère solution avec friend
Dans la définition de la classe, vous ajoutez le prototype de l’opérateur ==:
3131
Les opérateurs de comparaison
class Complexe{
friend operator==(Complexe const& a, Complexe const& b);
private:
double real;
double img;
public:
Complexe(double, double);
Complexe();
//…
};
bool operator==(Complexe const& a, Complexe const& b) {
return (a.real == b.real && a.img == b.img);
}
32. L'implémentation de l'opérateur==
2ème solution avec méthode d’instance
On commence par créer une méthode publique estEgal() qui renvoie true si b est
égal à l'objet dont on a appelé la méthode:
bool Complexe::estEgal(Complexe const& b){
return (real == b.real && img == b. img);
}
Et on utilise cette méthode dans l'opérateur de comparaison :
bool operator==(Complexe const& a, Complexe const& b) {
return a.estEgal(b);
}
3232
Les opérateurs de comparaison
33. L'implémentation de l'opérateur==
2ème solution avec méthode d’instance
À côté de la classe, vous rajoutez la définition de l’opérateur ==:
3333
Les opérateurs de comparaison
class Complexe{
private:
double real;
double img;
public:
Complexe(double, double);
Complexe();
bool estEgal(Complexe const&);
//…
};
bool operator==(Complexe const& a, Complexe const& b) {
return a.estEgal(b);
}
34. L'implémentation de l'opérateur==
Dans le main(), on peut faire un simple test de comparaison pour vérifier que l'on a
fait les choses correctement :
Output:
Les nombres complexes sont identiques
3434
Les opérateurs de comparaison
int main(){
Complexe z1(1.0, 2.0), z2(1.0, 2.0);
if (z1 == z2)
cout << "Les nombres complexes sont identiques";
else
cout << "Les nombres complexes sont différents";
return 0;
}
35. Operateur !=
Pour tester si deux objets sont différents, il suffit de tester s'ils ne sont pas égaux!
Voici la définition de la fonction operator==:
bool operator!=(Complexe const& a, Complexe const& b) {
if (a == b) //On utilise l'opérateur == qu'on a défini précédemment !
return false; //S'ils sont égaux, alors ils ne sont pas différents
else
return true; //Et s'ils ne sont pas égaux, c'est qu'ils sont différents
}
Ou en version courte
bool operator!=(Complexe const& a, Complexe const& b) {
return !(a==b); //On utilise l'opérateur == qu'on a défini précédemment !
}
3535
Les opérateurs de comparaison
36. L'implémentation de l'opérateur!=
Dans la définition de la classe, vous rajoutez le prototype de l’opérateur !=:
3636
Les opérateurs de comparaison
class Complexe{
friend operator==(Complexe const& a, Complexe const& b);
friend operator!= (Complexe const& a, Complexe const& b);
private:
double real;
double img;
public:
//…
};
bool operator==(Complexe const& a, Complexe const& b) {
return (a.real == b.real && a.img == b. img);
}
bool operator!=(Complexe const& a, Complexe const& b) {
return !(a==b); //On utilise l'opérateur ==
}
37. L'implémentation de l'opérateur!=
Dans le main(), on peut faire un simple test de comparaison:
Output:
Les nombres complexes sont identiques
3737
Les opérateurs de comparaison
int main(){
Complexe z1(1.0, 2.0), z2(1.0, 2.0);
if (z1 != z2)
cout << "Les nombres complexes sont différents ";
else
cout << "Les nombres complexes sont identiques ";
return 0;
}
38. Les autres opérateurs de comparaison
Il nous reste encore quatre autres opérateurs de comparaison: <, >, <= et >=.
Pour vous aider, je vous donne les prototypes:
bool operator<(NomDeLaClasse const &a, NomDeLaClasse const& b);
bool operator>(NomDeLaClasse const &a, NomDeLaClasse const& b);
bool operator<=(NomDeLaClasse const &a, NomDeLaClasse const& b);
bool operator>=(NomDeLaClasse const &a, NomDeLaClasse const& b);
3838
Les opérateurs de comparaison
39. Pour surcharger un opérateur Op dans une classe NomDeLaClasse, il
faut ajouter la méthode operatorOp dans la classe en question :
class NomDeLaClasse{
...
// prototype de l’opérateur Op
type_de_retour operatorOp(types_de_parametres);
...
};
// définition de l’opérateur Op
type_de_retour NomDeLaClasse::operatorOp(types_de_parametres) {
...
}
3939
Surcharge interne
43. 4343
Lien entre opérateurs
L’on veut exprimer le lien sémantique des opérateurs + et += (dans les
deux cas, la même opération somme)
L’opérateur += est par nature un opérateur demandant moins de
traitement car il ne créé pas de nouvel objet Complexe.
Toujours, il vaut mieux de définir le plus lourd en fonction du plus
léger.
44. 4444
Lien entre opérateurs
Exemple:
const Complexe operator+(Complexe z1, Complexe const& z2){
z1+=z2; // utilise l'opérateur += redéfini précédemment
return z1;
}
Le paramètre z1 étant passé par valeur, ici z1 est locale à la fonction.
47. 4747
Lien entre opérateurs
Exemple (3/3):
int main(){
Complexe z1(1.5, 2.5), z2(1.3, 1.4), z3;
z3=z1+z2;
cout << z3;
return 0;
}
Output:
(2.8, 3.9)
48. 4848
Surcharge interne ou Surcharge externe?
Les opérateurs propres à une classe peuvent être surchargés en interne
ou en externe :
z3 = z1 + z2; SOIT z3 = z1.operator+(z2);
SOIT z3 = operator+(z1, z2);
50. 5050
Préférez la surcharge externe chaque fois que vous pouvez le faire
SANS friend
c.-à-d. chaque fois que vous pouvez écrire l’opérateur à l’aide de
l’interface de la classe (et sans copies inutiles).
Préférez la surcharge interne si l’opérateur est « proche de la classe »,
c.-à-d. nécessite des accès internes ou des copies supplémentaires
inutiles (typiquement operator+=).
Surcharge interne ou Surcharge externe?
51. 5151
Exemples:
bool operator==(NomDeLaClasse const&) const; // ex: p == q
bool operator<(NomDeLaClasse const&) const; // ex: p < q
NomDeLaClasse& operator+=(NomDeLaClasse const&); // ex: p += q
NomDeLaClasse& operator-=(NomDeLaClasse const&); // ex: p -= q
NomDeLaClasse& operator*=(autre_type const); // ex: p *= x;
NomDeLaClasse& operator++(); // ex: ++p
NomDeLaClasse& operator++(int inutile); // ex: p++
const NomDeLaClasse operator-() const; // ex: r = -p;
//Surcharges externes
const NomDeLaClasse operator+(NomDeLaClasse, NomDeLaClasse const&); // r = p + q
const NomDeLaClasse operator-(NomDeLaClasse, NomDeLaClasse const&); // r = p - q
ostream& operator<<(ostream&, NomDeLaClasse const&); // ex: cout << p;
const NomDeLaClasse operator*(autre_type, NomDeLaClasse const&); // ex: q = x * p;
Surcharges de quelques opérateurs usuels
52. 5252
const Complexe operator+(Complexe, Complexe const&);
z3 = z1 + z2;
++(z1 + z2); // pas de sens
z1 + z2 = f(x); // idem
Pourquoi const en type de retour ?
54. 5454
z1 += z2;
void Complexe::operator+=(Complexe const&);
En C++, chaque expression fait quelque chose et vaut quelque chose :
x = Expression; z3 = (z1 += z2);
class Complexe {
// ...
Complexe& operator+=(Complexe const& z2);
// ...
};
Complexe& Complexe::operator+=(Complexe const& z2){
real += z2.real;
img += z2.real;
return *this;
}
this étant un pointeur sur un objet,*this est l'objet lui-même ! Notre opérateur
renvoie donc l'objet lui-même.
Quel type de retour pour operator+= ?
55. 5555
Attention de ne pas utiliser la surcharge des opérateurs à mauvais
escient et à veiller à les écrire avec un soin particulier.
Les performances du programme peuvent en être gravement affectées
par des opérateurs surchargés mal écrits.
En effet, l’utilisation inconsidérée des opérateurs peut conduire à un
grand nombre de copies d’objets :
Utiliser des références dès que cela est approprié !
Avertissement
56. 5656
Exemple:
Comparez le code suivant qui fait de 1 à 3 copies inutiles :
Complexe Complexe::operator+=(Complexe z2){
Complexe z3;
real += z2.real;
img += z2.img;
z3 = *this;
return z3;
}
Avec le code suivant qui n’en fait pas :
Complexe& Complexe::operator+=(Complexe const& z2){
real += z2.real;
img += z2.img;
return *this;
}
Avertissement
57. 5757
L’opérateur d’affectation = (utilisé par exemple dans a = b) :
est le seul opérateur universel (il est fourni de toutes façons par défaut
pour toute classe)
est très lié au constructeur de copie, sauf que le premier s’appelle
lors d’une affectation et le second lors d’une initialisation
la version par défaut, qui fait une copie de surface, est suffisante dans
la très grande majorité des cas
si nécessaire, on peut supprimer l’opérateur d’affectation :
class ClasseNonAffectation {
// ...
private:
ClasseNonAffectation& operator=(ClasseNonAffectation const&) = delete;
};
Opérateur d’affectation
58. 5858
Remarque:
Ne confondez pas le constructeur de copie avec la surcharge de
l'opérateur d’affectation = (operator=). Ils se ressemblent beaucoup
mais il y a une différence : le constructeur de copie est appelé lors
de l'initialisation (à la création de l'objet) tandis que la
méthode operator= est appelée si on essaie d'affecter un autre objet
par la suite, après son initialisation.
Exemple:
Complexe z1 = z2; // constructeur de copie
z1 = z2; // opérateur d’affectation: operator=
Opérateur d’affectation
59. 5959
L’opérateur d’affectation (méthode operator=) effectue le même
travail que le constructeur de copie. Si l’on doit redéfinir
l’opérateur d’affectation, son implémentation est donc relativement
simple:
NomDeLaClasse& NomDeLaClasse::operator=(NomDeLaClasse const& source)
{
if (this != &source)
//On vérifie que l'objet n'est pas le même que celui reçu en argument
{
//On copie tous les champs
…
}
return *this; //On renvoie l'objet lui-même
}
Surcharge de l’opérateur d’affectation
60. 6060
Depuis C++11, pour implémenter l’opérateur d’affectation =, on
choisit le schéma suivant :
on commencera par définir une fonction swap() pour échanger 2
objets de la classe, (sûrement en utilisant celle de la bibliothèque
utility (#include <utility>) sur les attributs)
puis on définira l’opérateur d’affectation comme suit :
NomDeLaClasse& NomDeLaClasse::operator=(NomDeLaClasse source)
// Notez le passage par VALEUR
{
swap(*this, source);
return *this;
}
Surcharge de l’opérateur d’affectation