Aller au menu - Aller au contenu

[Plan du site] Vous êtes ici --- > Le Site du Zéro > Les tutoriels > Officiels > Programmation > Apprenez à programmer en C++ ! > [Théorie] La Programmation Orientée Objet > La surcharge d'opérateurs > Lecture du tutoriel

La surcharge d'opérateurs

Avatar
Auteur : M@teo21
Note : 19 / 20 (20 votes)
Visualisations : 76 386


Plus d'informations Plus d'informations
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 :)
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Petits préparatifs

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 :p ), 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 o_O
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 :D

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 ! :zorro:

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.

Les opérateurs arithmétiques (+, -, *, /, %)

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 ? o_O


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 :D
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à ? :D )

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 :D


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 :




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++
1
resultat = duree + 30;


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 ! :p


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à :


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 :


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.

Si multiplier une Duree par une Duree n'a pas de sens, en revanche on peut imaginer que l'on multiplie une Duree par un nombre entier. Ainsi, l'opération 2h25m50s * 3 est envisageable. Attention à utiliser le bon prototype, en l'occurence :
Code : C++
1
Duree operator*(int nombre);



Les opérateurs de comparaison (==, >, <, ...)

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 :



L'opérateur d'affectation (=)

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++
1
monObjet = unAutreObjet;


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é :



J'espère avoir été suffisamment clair :D
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.

Les opérateurs de flux (<<, >>)

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++
1
cout << "Coucou !";


... que :

Code : C++
1
cout << 15;


(et c'est là qu'on dit "merci la surcharge des méthodes !" :p )

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.

Lorsque vous incluez <iostream>, un objet cout est automatiquement déclaré comme ceci :
Code : C++
1
ostream cout;

ostream est la classe, cout est l'objet. C'est donc la classe ostream qu'il faudrait théoriquement retoucher pour pouvoir créer une nouvelle surcharge de l'opérateur <<... mais on n'a pas le droit car on n'a pas accès au code définissant la classe ostream !

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 :

On va opter ici pour la seconde solution :p
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 :D
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
2h25m28s et 0h16m33s


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 :


Q.C.M.

Quel est le nom de la méthode correspondant à l'opérateur >= ?
Que fait l'opérateur d'affectation par défaut écrit par le compilateur ?
Que doit renvoyer la méthode correspondant à l'opérateur == ?
A quelle opération correspond ce code ?


Code : C++
1
resultat = tempsLimite.operator-(chrono);

Statistiques de réponses au QCM


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 :


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 !
Chapitre précédent Sommaire Chapitre suivant
Retour en haut Retour en haut


Créé : le 18/09/2007 à 17:13:58
Modifié : le 23/10/2008 à 14:17:50
Avancement : 100%
Licence : Copie non autorisée

37 commentaires

Changer de design | En savoir plus | Plan du site | Politique d'accessibilité | Règles | RSS tutoriels | RSS news
Édité par Simple IT SARL : Nous contacter | Notre blog | Revue de presse | Publicité

Y'a plus rien à lire, faut remonter maintenant !

Hébergement web - Correction de tutoriels - Créer un site
Vous souhaitez apparaître ici ? Contactez-nous.

Nombre de connectés 176 Zéros connectés | Requêtes SQL 8 requêtes | Temps de génération de la page : Total (SQL) 0.0287s (0.0136s)