La surcharge d'opérateurs
On l'a vu, le langage C++ propose beaucoup de nouveautés qui peuvent se révéler très utiles, si on arrive à s'en servir correctement (je pense par exemple à la surcharge de fonctions).
Une des nouveautés les plus étonnantes est la
surcharge des opérateurs, que nous allons étudier dans ce chapitre. C'est une technique qui permet de réaliser des opérations mathématiques intelligentes entre vos objets lorsque vous utilisez dans votre code des symboles comme +, -, *, etc.
Au final, votre code sera plus court et plus clair, et gagnera donc en lisibilité vous allez voir
Qu'est-ce que c'est ?
Le principe est très simple. Supposons que vous ayez créé une classe pour stocker une durée (ex. : 4h23m), et que vous avez 2 objets de type Duree. Vous voulez les additionner entre eux pour connaître la durée totale.
En temps normal, il faudrait créer une fonction "additionner" :
Code : C++1 | resultat = additionner(duree1, duree2);
|
La fonction additionner ferait ici la somme de
duree1 et
duree2 et stockerait ça dans
resultat.
Ca fonctionne, mais ce n'est pas franchement lisible. Ce que je vous propose dans ce chapitre, c'est d'être capable d'écrire ça :
Code : C++1 | resultat = duree1 + duree2;
|
En clair, on fait ici comme si nos objets étaient de simples "nombres". Mais comme un objet c'est plus complexe qu'un nombre (vous avez eu l'occasion de vous en rendre compte

), il va falloir expliquer à l'ordinateur comment effectuer l'opération.
La classe Duree pour nos exemples
Toutes les classes ne sont pas forcément adaptées à la surcharge d'opérateurs. Ainsi, ajouter des objets de type Personnage entre eux serait pour le moins un peu louche
Nous allons donc changer d'exemple, ça sera l'occasion de vous aérer un peu l'esprit sinon vous allez finir par croire que le C++ ne sert qu'à créer des RPG
Cette classe Duree sera capable de stocker des heures, des minutes et des secondes. Rassurez-vous, c'est une classe relativement facile à écrire (plus facile que Personnage en tout cas !), ça ne devrait vous poser aucun problème si vous avez compris les chapitres précédents.
Duree.h
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | #ifndef DEF_DUREE
#define DEF_DUREE
class Duree
{
public:
Duree(int heures = 0, int minutes = 0, int secondes = 0);
private:
int m_heures;
int m_minutes;
int m_secondes;
};
#endif
|
Chaque objet de type Duree stockera un certain nombre d'heures, minutes et secondes.
Vous noterez que j'ai utilisé des valeurs par défaut au cas où l'utilisateur aurait la flemme de les préciser

On pourra donc créer un objet de plusieurs façons différentes :
Code : C++1
2
3
4 | Duree chrono; // Pour stocker 0 heures, 0 minutes et 0 secondes
Duree chrono(5); // Pour stocker 5 heures, 0 minutes et 0 secondes
Duree chrono(5, 30); // Pour stocker 5 heures, 30 minutes et 0 secondes
Duree chrono(0, 12, 55); // Pour stocker 0 heures, 12 minutes et 55 secondes
|
Duree.cpp
L'implémentation de notre constructeur est expédiée en 30 secondes montre en main
Code : C++1
2
3
4
5 | #include "Duree.h"
Duree::Duree(int heures, int minutes, int secondes) : m_heures(heures), m_minutes(minutes), m_secondes(secondes)
{
}
|
Et dans main.cpp ?
Pour l'instant notre main.cpp ne va déclarer que 2 objets de type Duree, que j'initialise un peu au pif :
Code : C++1
2
3
4
5
6 | int main()
{
Duree duree1(0, 10, 28), duree2(0, 15, 2);
return 0;
}
|
Voilà, nous sommes prêts à affronter les surcharges d'opérateurs maintenant !
Les plus perspicaces d'entre vous auront remarqué que rien ne m'interdit de créer un objet avec 512 minutes et 1455 secondes. En effet, on peut écrire Duree chrono(0, 512, 1455); sans être inquiété. Normalement, cela devrait être interdit, ou tout du moins notre constructeur devrait être assez intelligent pour "découper" les minutes et les convertir en heures/minutes, et de même pour les secondes, afin qu'elles ne dépassent pas 60.
Je ne le fais pas ici, mais je vous encourage à modifier votre constructeur pour faire cette conversion si nécessaire, ça vous entraînera ! Etant donné qu'il faut faire des if et quelques petites opérations mathématiques dans le constructeur, vous ne pourrez pas utiliser de liste d'initialisation.
Nous allons commencer par voir les opérateurs mathématiques les plus classiques, à savoir l'addition, la soustraction, la multiplication, la division et le modulo.
Une fois que vous aurez appris à vous servir de l'un d'entre eux, vous verrez que vous saurez vous servir de tous les autres
Pour être capable d'utiliser le symbole "+" entre 2 objets, vous devez créer une méthode ayant précisément pour nom
operator+ qui a pour prototype :
Code : C++1 | Objet operator+(const Objet &monObjet);
|
La méthode reçoit donc une référence sur l'objet (constant, donc on ne peut pas le modifier) à additionner.
Dans notre classe Duree, on doit donc rajouter cette méthode (ici dans le .h) :
Code : C++1 | Duree operator+(const Duree &duree);
|
Mode d'utilisation
Comment ça marche ce truc ?

Dès le moment où vous avez créé cette méthode operator+, vous pouvez additionner 2 objets de type Duree entre eux :
Code : C++1 | resultat = duree1 + duree2;
|
Ce n'est pas de la magie. En fait le compilateur "traduit" ça par :
Code : C++1 | resultat = duree1.operator+(duree2);
|
... ce qui est beaucoup plus classique et compréhensible pour lui

Il appelle donc la méthode operator+ de l'objet duree1, et envoie duree2 en paramètre à la méthode. La méthode, elle, va retourner un résultat de type Duree.
Implémentation
L'implémentation n'est pas vraiment compliquée, mais il va quand même falloir réfléchir un peu. En effet, ajouter des secondes, minutes et heures ça va, mais il faut faire attention à la retenue si ça dépasse 60.
Je vous recommande d'essayer d'écrire la méthode vous-même, c'est un excellent exercice algorithmique, ça entretient le cerveau, ça vous rend meilleur programmeur (je vous ai convaincus là ?

)
Voici ce que donne mon implémentation pour ceux qui ont besoin de la solution :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | Duree Duree::operator+(const Duree &duree)
{
int heures = m_heures;
int minutes = m_minutes;
int secondes = m_secondes;
// 1 : ajout des secondes
secondes += duree.m_secondes; // Exceptionnellement autorisé car même classe
// Si le nombre de secondes dépasse 60, on rajoute des minutes et on met un nombre de secondes inférieur à 60
minutes += secondes / 60;
secondes %= 60;
// 2 : ajout des minutes
minutes += duree.m_minutes;
// Si le nombre de minutes dépasse 60, on rajoute des heures et on met un nombre de minutes inférieur à 60
heures += minutes / 60;
minutes %= 60;
// 3 : ajout des heures
heures += duree.m_heures;
// Création de l'objet resultat et retour
Duree resultat(heures, minutes, secondes);
return resultat;
}
|
Ce n'est pas un algorithme ultracomplexe, mais comme je vous avais dit il faut réfléchir un tout petit peu pour pouvoir l'écrire quand même
On commence par créer et initialiser 3 variables locales (heures, minutes et secondes) qui correspondent au résultat. Ce résultat, on le mettra dans un objet de type Duree que l'on renverra à la fin.
On a initialisé ces 3 variables avec la valeur de l'objet sur lequel on travaille (duree1 si on se fie à l'exemple donné un peu plus haut).
On va y ajouter les heures, minutes et secondes de l'objet reçu en paramètre, à savoir duree2. Comme on l'avait vu dans le chapitre précédent, on a exceptionnellement le droit d'accéder directement aux attributs de cet objet car on se trouve dans une méthode de la même classe. C'est un peu tordu mais ça nous aide bien (sinon il aurait fallu créer des méthodes "accesseur" comme getHeures()).
Rajouter les secondes, c'est facile. Mais ensuite on doit rajouter un reste si on a dépassé 60 secondes (donc rajouter des minutes). Je ne vous explique pas comment ça fonctionne dans le détail, je vous laisse vous remuer les méninges un peu, ce n'est vraiment pas bien difficile (c'est du niveau des tous premiers chapitres du cours

). Vous noterez que c'est un cas où l'opérateur modulo (%), à savoir le reste de la division, est très utile.
Bref, on fait de même avec les minutes, et quant aux heures c'est encore plus facile vu qu'il n'y a pas de reste (on peut dépasser les 24 heures donc pas de problème).
Quelques tests
Pour mes tests, j'ai dû rajouter une méthode
afficher() à la classe Duree (elle fait un cout de la durée tout bêtement).
Voilà mon bôôô main

:
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | #include <iostream>
#include "Duree.h"
using namespace std;
int main()
{
Duree duree1(0, 10, 28), duree2(0, 15, 2);
Duree resultat;
duree1.afficher();
cout << "+" << endl;
duree2.afficher();
resultat = duree1 + duree2;
cout << "=" << endl;
resultat.afficher();
return 0;
}
|
Et le tant attendu résultat à l'écran :
Code : Console | 0h10m28s
+
0h15m2s
=
0h25m30s |
Cool, ça marche

Bon mais ça c'était trop facile, il n'y avait pas de reste dans mon calcul. Corsons un peu les choses avec d'autres valeurs :
Code : Console | 1h45m50s
+
1h15m50s
=
3h1m40s |
Yeahhh ! Ca marche ! (et du premier coup pour moi nananère

)
J'ai bien entendu testé d'autres valeurs pour être bien sûr que ça fonctionnait, mais de toute évidence ça marche très bien et mon algo est donc bon
Bon, on en viendrait presque à oublier l'essentiel dans tout ça. Tout ce qu'on a fait là, c'était pour pouvoir écrire cette ligne :
Code : C++1 | resultat = duree1 + duree2;
|
La surcharge de l'opérateur + nous a permis de rendre notre code clair, simple et lisible, alors qu'on aurait dû utiliser une méthode en temps normal.
Télécharger le projet
Pour ceux d'entre vous qui n'auraient pas bien suivi la procédure, ou qui sont tout simplement fainéants (

), je vous propose de télécharger le projet contenant :
- main.cpp
- Duree.cpp
- Duree.h
- Ainsi que le fichier .cbp de Code::Blocks (si vous utilisez cet IDE comme moi)
Bonus track #1
Ce qui est vraiment sympa dans tout ça, c'est que tel que notre système est fait, on peut très bien additionner plusieurs durées en même temps sans aucun problème.
Par exemple, je rajoute juste une troisième durée dans mon main et je l'additionne avec les autres :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | int main()
{
Duree duree1(1, 45, 50), duree2(1, 15, 50), duree3 (0, 8, 20);
Duree resultat;
duree1.afficher();
cout << "+" << endl;
duree2.afficher();
cout << "+" << endl;
duree3.afficher();
resultat = duree1 + duree2 + duree3;
cout << "=" << endl;
resultat.afficher();
return 0;
}
|
Code : Console | 1h45m50s
+
1h15m50s
+
0h8m20s
=
3h10m0s |
C'est cool non vous trouvez pas ?
En fait, la ligne-clé :
Code : C++1 | resultat = duree1 + duree2 + duree3;
|
... revient à écrire :
Code : C++1 | resultat = duree1.operator+(duree2.operator+(duree3));
|
Le tout s'imbrique dans une logique implacable et vient se placer finalement dans l'objet resultat
Notez que le C++ ne vous permet pas de changer la priorité des opérateurs.
Vous voyez ici un exemple un peu plus mathématique (mais pas vraiment compliqué) de l'intérêt de la POO. En C, la même chose était faisable, mais on aurait mélangé les heures, minutes et secondes de chacun. Ici, tout est regroupé et chaque "Duree" est suffisamment intelligente pour être capable de s'additionner avec d'autres durées (et de faire bien d'autres choses encore !).
Bonus track #2
Et pour notre seconde bonus track, sachez qu'on n'est pas obligé d'additionner des Duree avec des Duree, du temps que ça reste logique et compatible.
Par exemple, on pourrait très bien additionner une Duree et un int. On considérerait dans ce cas que le nombre int est un nombre de secondes à ajouter.
Cela nous permettra d'écrire par exemple :
Code : C++
Vive la surcharge des fonctions !
Code : C++1 | Duree operator+(const int secondes);
|
... mais vous croyiez tout de même pas que j'allais vous écrire l'implémentation. Allez hop hop hop au boulot !
Les autres opérateurs arithmétiques
Maintenant que vous avez vu assez en détail le cas d'un opérateur (celui d'addition pour ceux qui ont la mémoire courte

), vous allez voir que pour la plupart des autres opérateurs c'est très facile et qu'il n'y a pas de difficulté supplémentaire. Le tout est de s'en servir correctement pour la classe que l'on manipule.
Ces opérateurs sont du même "type" que l'addition. Vous les connaissez déjà :
- La soustraction (-)
- La multiplication (*)
- La division (/)
- Le modulo (%), c'est-à-dire le reste de la division
Pour surcharger ces opérateurs, rien de plus simple : créez une méthode dont le nom commence par operator suivi de l'opérateur en question. Cela donne donc :
- operator-
- operator*
- operator/
- operator%
Pour notre classe Duree, il peut être intéressant de définir la soustraction (operator-).
Je vous laisse le soin de le faire, en vous basant sur l'addition ça ne devrait pas être trop compliqué
En revanche, les autres opérateurs ne servent a priori à rien : en effet, on ne multiplie pas des durées entre elles, et on les divise encore moins. Comme quoi, tous les opérateurs ne sont pas utiles à toutes les classes : ne définissez donc que ceux qui vous seront vraiment utiles.
Ces opérateurs vont vous permettre de comparer des objets entre eux. Le plus utilisé d'entre eux est probablement l'opérateur d'égalité (==) qui permet de vérifier si 2 objets sont égaux. C'est à vous d'écrire le code de la méthode qui détermine si les objets sont identiques, l'ordinateur ne peut pas le deviner pour vous car il ne connaît pas la "logique" de vos objets
Tous ces opérateurs de comparaison ont un point en commun particulier :
ils renvoient un booléen (
bool) et non un objet comme c'était le cas des autres opérateurs.
L'opérateur ==
On va écrire l'implémentation de l'opérateur d'égalité pour commencer, vous allez voir que c'est très simple :
Code : C++1
2
3
4
5
6
7 | bool Duree::operator==(const Duree &duree)
{
if (m_heures == duree.m_heures && m_minutes == duree.m_minutes && m_secondes == duree.m_secondes)
return true;
else
return false;
}
|
On compare à chaque fois un attribut de l'objet dans lequel on se trouve avec un attribut de l'objet auquel on se compare (les heures avec les heures, les minutes avec les minutes...). Si ces 3 valeurs sont identiques alors on peut considérer que les objets sont identiques et renvoyer true (vrai).
Dans le main, on peut faire un simple test de comparaison pour vérifier si on a fait les choses correctement :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11 | int main()
{
Duree duree1(0, 10, 28), duree2(0, 10, 28);
if (duree1 == duree2)
cout << "Les durees sont identiques";
else
cout << "Les durees sont differentes";
return 0;
}
|
Résultat :
Code : Console | Les durees sont identiques |
L'opérateur <
Je vous préviens on va pas tous les faire sinon on y est encore demain
Si l'opérateur == peut s'appliquer à la plupart des objets, il n'est pas certain que l'on puisse dire de tous nos objets qui est le plus grand. Tous n'ont pas forcément une notion de grandeur, prenez par exemple notre classe Personnage, il serait je pense assez stupide de vouloir vérifier si un Personnage est "inférieur" à un autre ou non (à moins que vous ne compariez les vies... à vous de voir).
En tout cas avec la classe Duree on a de la chance, il est facile et "logique" de vérifier si une Duree est inférieure à une autre.
Voici mon implémentation pour l'opérateur "est strictement inférieur à" (<) :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11 | bool Duree::operator<(const Duree &duree)
{
if (m_heures < duree.m_heures)
return true;
else if (m_heures == duree.m_heures && m_minutes < duree.m_minutes)
return true;
else if (m_heures == duree.m_heures && m_minutes == duree.m_minutes && m_secondes < duree.m_secondes)
return true;
else
return false;
}
|
Avec un peu de réflexion on finit par trouver cet algorithme, il suffit d'activer un peu ses méninges

Vous noterez que la méthode renvoie false si les durées sont identiques : c'est normal, car il s'agit de l'opérateur "strictement inférieur à" (<). En revanche, si ça avait été la méthode de l'opérateur "inférieur ou égal à" (<=), il aurait fallu renvoyer true.
Je vous laisse le soin de tester dans le main si ça fonctionne correctement
Les autres opérateurs de comparaison
On ne va pas les implémenter ici, ça surchargerait inutilement. Par contre, je vous invite à essayer de les implémenter pour notre classe Duree, ça fera un bon exercice d'algorithmie. Il reste notamment :
- operator>
- operator<=
- operator>=
- operator!=
Un des principaux pièges de ce chapitre vient de l'opérateur "=" que l'on peut lui aussi surcharger. C'est l'opérateur d'affectation, qui permet donc de donner une valeur à un objet.
En fait, le "piège" vient du fait que vous risquez de le confondre avec le constructeur de copie. C'est pourquoi je vais insister plus précisément sur cet opérateur pour que vous voyiez bien la différence et ce à quoi il sert.
Rappel : le constructeur de copie
Quand le constructeur de copie est-il appelé ? Vous vous en souvenez ?
Il y a en fait plusieurs cas, les 3 principaux étant :
1/ Lors de l'appel explicite au constructeur de copie
Lorsque vous déclarez un objet et que vous indiquez en paramètre un autre objet, le constructeur de copie est appelé :
Code : C++1
2 | Objet monObjet;
Objet copieObjet(monObjet); // Appel du constructeur de copie
|
2/ Lors d'une affectation au moment de la déclaration
C'est pareil, sauf qu'on utilise le signe "=" ce qui rend le code plus lisible :
Code : C++1
2 | Objet monObjet;
Objet copieObjet = monObjet; // Appel du constructeur de copie
|
3/ Lors d'un appel de fonction qui prend un objet en paramètre
Si la fonction prend un objet (et non pas un pointeur ni une référence) en paramètre, l'objet est "copié" spécialement pour la fonction. Il y a appel du constructeur de copie avant le début de la fonction.
Code : C++1
2
3
4 | void maFonction (Objet copieOjbet) // Appel du constructeur de copie
{
}
|
Le rapport avec la surcharge de l'opérateur = ?
Si elle existe, la méthode
operator= sera appelée dès qu'on essaie d'affecter une valeur à notre objet.
Par exemple, si à un moment dans le code on affecte à notre objet la valeur d'un autre objet :
Code : C++
Mais attends... C'est pas le constructeur de copie qui sera appelé là ?
Non justement, c'est là qu'est le piège. Dans le cas n°2 vu plus haut, c'est lors de la déclaration de l'objet qu'on fait une affectation. Dans ce cas, c'est le constructeur de copie qui est appelé.
En revanche, dans tout le reste du code, si on affecte une valeur à notre objet, c'est cette fois la méthode operator= qui sera appelée.
Donc en résumé :
- Lors de la déclaration (création) de notre objet, si vous utilisez le signe "=" pour lui affecter immédiatement la valeur d'un autre objet, c'est le constructeur de copie qui est appelé.
Code : C++1 | Objet copieObjet = monObjet; // Déclaration de l'objet
|
- Après, à n'importe quel autre moment, si vous décidez d'affecter la valeur d'un autre objet à votre objet, c'est la méthode surchargeant l'opérateur = (operator=) qui sera appelée.
Code : C++1 | copieObjet = monObjet; // Affectation APRES la déclaration
|
J'espère avoir été suffisamment clair

Cela signifie donc qu'en général vous devrez écrire le code du constructeur de copie et de l'opérateur "=" en même temps si vous voulez qu'à n'importe quel moment dans votre code on puisse faire une affectation sur votre objet.
Implémentation de la méthode operator= pour la classe Duree
Puisqu'on y est, implémentons la méthode pour la classe Duree et on sera tranquille
Code : C++1
2
3
4
5
6
7
8 | Duree Duree::operator=(const Duree &duree)
{
m_heures = duree.m_heures;
m_minutes = duree.m_minutes;
m_secondes = duree.m_secondes;
return *this;
}
|
Vous noterez que la méthode renvoie l'objet lui-même. En effet, souvenez-vous, this est un pointeur vers l'objet. Si on écrit *this, c'est donc l'objet lui-même que l'on renvoie. Cela permet de traiter le cas où on chaîne les affectations :
Code : C++1 | objet1 = objet2 = objet3;
|
Ce n'est pas très courant mais mieux vaut être prévoyant.
A part ça c'est tout bête. Et dans le même temps, si ce n'est pas fait, je vous conseille d'écrire le constructeur de copie pour que le signe "=" fonctionne dans tous les cas. Le code devrait être quasiment le même.
Le compilateur écrit un opérateur d'affectation par défaut automatiquement, mais c'est un opérateur "bête". Cet opérateur bête se contente de copier les valeurs des attributs un à un dans le nouvel objet.
Je sais ce que vous allez me dire : c'est exactement ce qu'on vient de faire ! En effet, dans notre cas réécrire l'opérateur d'affectation n'était donc pas nécessaire. En revanche, ça l'aurait été si on avait eu par exemple des pointeurs et qu'il avait fallu faire des allocations de mémoire, afin d'éviter les problèmes expliqués dans le chapitre précédent.
Parmi les nombreuses choses qui ont dû vous choquer quand vous avez commencé le C++, dans la catégorie "
oulah c'est bizarre ça mais on verra plus tard", il y a les flux d'entrée-sortie. Derrière ce nom barbare se cachent ces petits symboles
>> et
<<.
Quand les utilise-t-on ? Allons allons, vous n'allez pas me faire croire que vous avez la mémoire si courte
Code : C++1
2 | cout << "Coucou !";
cin >> variable;
|
Figurez-vous justement que << et >> sont des opérateurs. Le code ci-dessus revient donc à écrire :
Code : C++1
2 | cout.operator<<("Coucou !");
cin.operator>>(variable);
|
On a donc fait appel aux méthodes operator<< et operator>> des objets cout et cin !
Définir ses propres flux pour cout
Nous allons ici nous intéresser plus particulièrement à l'opérateur << utilisé avec cout.
Les opérateurs de flux sont définis par défaut pour les types de variables int, double, char*, ainsi que pour les objets comme string. C'est ainsi que l'on peut aussi bien écrire :
Code : C++
... que :
Code : C++
(et c'est là qu'on dit "merci la surcharge des méthodes !"
)
Bon, le problème c'est que cout ne connaît pas votre classe flambant neuve Duree, et donc qu'il ne possède pas de méthode surchargée pour les objets de ce type. On ne peut donc pas écrire :
Code : C++1
2 | Duree chrono(0, 2, 30);
cout << chrono; // Erreur : il n'existe pas de méthode cout.operator<<(Duree &duree)
|
Qu'à cela ne tienne, nous allons écrire cette méthode !
Quoi ?! Mais on ne peut pas modifier le code de cout non ?
Déjà si vous vous êtes posé la question, bravo, c'est que vous commencez à bien vous repérer. En effet, c'est une méthode de la classe ostream (dont l'objet cout est une instance) que l'on doit définir, et on n'a pas accès au code correspondant.
Par contre, il est possible de créer de simples fonctions (en dehors des objets) pour surcharger des opérateurs. C'est un peu particulier je le reconnais, mais on n'a pas le choix dans le cas présent.
Implémentation d'operator<< en tant que fonction
C'est donc une surcharge d'opérateur un peu particulière que nous allons faire : nous allons écrire une fonction, en dehors de toute classe donc, et non pas une méthode.
Comme c'est un cas assez particulier et que vous n'aurez pas à la reproduire tous les jours, je vous recommande de me suivre pas à pas.
Commencez par écrire cette fonction :
Code : C++1
2
3
4
5 | ostream &operator<<( ostream &out, Duree &duree )
{
out << duree.m_heures << "h" << duree.m_minutes << "m" << duree.m_secondes << "s"; // Erreur
return out;
}
|
Vous devriez la placer
avant le main (ou tout du moins son prototype), sinon le main ne la connaîtra pas.
Le premier paramètre (référence sur un objet de type ostream) qui vous sera automatiquement passé est en fait l'objet cout (que l'on appelle ici out dans la fonction pour éviter les conflits de nom). Le second paramètre est une référence vers l'objet de type Duree que vous tentez d'afficher en utilisant le flux <<.
La fonction doit récupérer les attributs qui l'intéressent dans l'objet et les envoyer à l'objet "out" (qui n'est autre que cout). Ensuite, elle retourne cet objet, ce qui permet de pouvoir faire une chaîne :
Code : C++1 | cout << duree1 << duree2;
|
Si je compile ça plante ! Ca me dit que je n'ai pas le droit d'accéder aux attributs de l'objet duree depuis la fonction !
Eh oui c'est parfaitement normal, car on est à l'
extérieur de la classe, et les attributs m_heures, m_minutes et m_secondes sont privés. On ne peut donc pas les lire de cet endroit du code.
2 solutions :
- Ou bien vous créez des accesseurs comme on l'a vu (ces fameuses méthodes getHeures, getMinutes...), ça marche bien mais c'est un peu ennuyeux à écrire
- Ou bien vous utilisez la technique que je vais vous montrer

On va opter ici pour la seconde solution

Changez la 1ère ligne de la fonction comme ceci :
Code : C++1
2
3
4
5 | ostream &operator<<( ostream &out, Duree &duree )
{
duree.afficher(out) ; // <- Changement ici
return out;
}
|
Et rajoutez une méthode afficher dans la classe Duree.
Prototype à mettre dans Duree.h :
Code : C++1 | void afficher(std::ostream &out);
|
Implémentation de la méthode dans Duree.cpp :
Code : C++1
2
3
4 | void Duree::afficher(ostream &out)
{
out << m_heures << "h" << m_minutes << "m" << m_secondes << "s";
}
|
On passe donc le relai à une méthode à l'intérieur de la classe, qui, elle, a le droit d'accéder aux attributs. La méthode prend en paramètre la référence vers l'objet out pour pouvoir lui envoyer les valeurs qui nous intéressent. Ce qu'on n'a pas pu faire dans la fonction operator<<, on le donne à faire à une méthode de la classe Duree.
Ouf ! Maintenant dans le main, que du bonheur !
Bon, c'était un peu gymnastique, mais maintenant c'est que du bonheur

Vous allez pouvoir dans votre main afficher vos objets de type Duree très simplement :
Code : C++1
2
3
4
5
6
7
8 | int main()
{
Duree duree1(2, 25, 28), duree2(0, 16, 33);
cout << duree1 << " et " << duree2 << endl;
return 0;
}
|
Résultat :
Code : Console
Enfantin

Comme quoi, on prend un peu de temps pour écrire la classe, mais ensuite quand on doit l'utiliser c'est extrêmement simple !
Si vous avez un peu du mal à vous repérer dans le code, ce que je peux comprendre, je mets à votre disposition le projet complet comme tout à l'heure dans ce zip :
Il y a énormément d'autres opérateurs surchargeables en C++, en fait presque tout peut être surchargé. Chaque opérateur étant particulier, il serait impossible de tout voir dans ce chapitre. Au moins avons-nous pu voir les principaux
A titre d'information, sachez qu'il est aussi possible de surcharger :
- new et delete : l'allocation dynamique, s'il y a besoin de faire des vérifications spéciales lors d'une allocation de mémoire
- & et * : opérateurs d'indirection et de déréférencement pour manipuler les pointeurs
- (int) et compagnie : opérateurs de transtypage
- ++ et -- : opérateurs d'incrémentation et de décrémentation
- [] : pour parcourir l'objet comme un tableau. Le type string s'en sert d'ailleurs pour que l'on puisse écrire monString[3] et ainsi accéder au 4ème caractère comme si c'était un tableau, alors que c'est en fait un objet. Malin, il fallait y penser !
- etc.
Bref, vous l'aurez compris, la surcharge des opérateurs est un outil puissant, pour ne pas dire très puissant si on commence à s'en servir sur l'allocation dynamique, le transtypage ou encore les opérateurs d'indirection et de déréférencement.
Mon conseil serait : ne faites la surcharge que si elle vous sera vraiment utile. C'est certes un outil puissant, mais il n'est pas nécessaire de le mettre à toutes les sauces. Votre classe doit proposer des fonctionnalités utiles et non pas farfelues !
Informations sur le tutoriel
Retour en haut
Créé : Le 18/09/2007 à 17:13:58
Modifié : Le 17/06/2009 à 11:20:29
Avancement : 100%
Licence : Copie non autorisée
47 commentaires