IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

Surcharge d'opérateur : Retour par référence


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2018
    Messages
    28
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 28
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2018
    Messages : 28
    Par défaut Surcharge d'opérateur : Retour par référence
    Bonjour,
    Je suis un débutant en C++ et je suis en train de voir la surcharge d'opérateur.
    Il y a une partie qui me pose problème, en effet, je ne comprends pas pourquoi il est impératif de mettre une référencer au type de sorti quand on veut pouvoir faire des appels en cascade.

    De ce que j'avais compris, une référence et l'objet de la référence sont équivalents et du coup, je ne comprends pas pourquoi les appels en cascade ne fonctionne pas si je ne mets pas la référence.

    Voici le code sur lequel je travaille :
    J'ai deux classes, Utilisateur et Groupe.

    Groupe.h
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    class Groupe {
    public:
        Groupe& operator+=(Utilisateur* user);
     
    private:
        vector<Utilisateur*> utilisateurs_;
    }
    Groupe.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    Groupe& Groupe::operator+=(Utilisateur* user) {
        utilisateurs_.push_back(user);
     
        return*this;
    }
    Dans le main.cpp je fais :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Utilisateur* u1 = new Utilisateur("Martin");
    Utilisateur* u2 = new Utilisateur("Franklin");
    Utilisateur* u3 = new Utilisateur("Geraldine");
    Utilisateur* u4 = new Utilisateur("Bernard");
    Utilisateur* u5 = new Utilisateur("Christian");
     
    Groupe* groupe = new Groupe("vacances");
     
    ((((*groupe += u1) += u2) += u3) += u4) += u5;
    Je ne comprends pas pourquoi si je supprime la référence de sorti et que j'écris : Groupe operator+=(Utilisateur* user); à la place de Groupe& operator+=(Utilisateur* user); (pas que dans le .h bien sûr), ça ne marche plus. A chaque +=, le programme crée un nouveau Groupe contenant à chaque fois un nouvel Utilisateur (le dernier += retourne un Groupe avec les 5 Utilisateurs) et à la fin de la ligne, 4 constructeurs sont appelés et le Groupe "groupe" ne contient qu'un utilisateur, u1.

    Je ne comprends pas en quoi retourner un Groupe à la place d'un Groupe& change quoi que ce soit. Si quelqu'un peut m'expliquer, je lui en serai très reconnaissant.

    Merci !

  2. #2
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 496
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 496
    Billets dans le blog
    1
    Par défaut
    Bonjour,

    Il faudrait définir "ça ne marche plus" avec un peu plus de détails...

    Il faut bien comprendre que ce veut dire "retourner *this par référence" et "retourner *this pas par référence". Dans le premier, tu renvoies l'objet en cours (une référence sur *this). Dans le second cas, que renvoies-tu ? Si tu ne renvoies pas une référence sur l'objet en cours, tu renvoies..... une copie ! Quand tu chaines les appels par copie, et bien tu modifies, tu copies, tu modifies, tu copies, et à la fin, si tu ne remets pas tout ça dans un objet que tu gardes et donc tu perds tout...

  3. #3
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut
    Bonjour,

    L'opérateur += est associatif à droite, donc n'est pas adapté pour chaîner les appels dans ce sens : ça t'oblige à écrire une tonne de parenthèses.
    En outre, pour la gestion de la mémoire, il n'y a pas besoin de mettre des new partout. Le C++ est différent du Java.

    Voici une autre version du code qui ne nécessite pas plus de prérequis en C++ :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    #include <string>
    #include <vector>
     
    class Utilisateur {
    public:
    	Utilisateur(std::string nom) :
    		nom_{nom}
    	{}
    private:
    	std::string nom_;
    };
     
    class Groupe {
    public:
    	Groupe(std::string nom) :
    		nom_{nom}
    	{}
    	Groupe& push_back(Utilisateur* user) {
    		utilisateurs_.push_back(user);
    		return *this;
    	}
    private:
    	std::string nom_;
    	std::vector<Utilisateur*> utilisateurs_;
    };
     
    int main()
    {
    	Utilisateur u1{"Martin"};
    	Utilisateur u2{"Franklin"};
    	Utilisateur u3{"Geraldine"};
    	Utilisateur u4{"Bernard"};
    	Utilisateur u5{"Christian"};
     
    	Groupe groupe{"vacances"};
     
    	groupe.push_back(&u1)
    	      .push_back(&u2)
    	      .push_back(&u3)
    	      .push_back(&u4)
    	      .push_back(&u5);
    }
    Plus tard, tu découvriras la range-based for loop qui permettra de remplacer ces appels de push_back par ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    	for(Utilisateur* user : {&u1, &u2, &u3, &u4, &u5})
    		groupe.push_back(user);
    En utilisant d'autres fonctionnalités que tu verras plus tard, le code pourra ressembler à :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    #include <functional>
    #include <string>
    #include <utility>
    #include <vector>
     
    class Utilisateur final {
    public:
    	explicit Utilisateur(std::string nom) :
    		nom_{std::move(nom)}
    	{}
    private:
    	std::string nom_;
    };
     
    class Groupe final {
    public:
    	explicit Groupe(std::string nom) :
    		nom_{std::move(nom)}
    	{}
    	void push_back(Utilisateur& user) {
    		utilisateurs_.push_back(user);
    	}
    private:
    	std::string nom_;
    	std::vector<std::reference_wrapper<Utilisateur>> utilisateurs_;
    };
     
    int main()
    {
    	Utilisateur u1{"Martin"};
    	Utilisateur u2{"Franklin"};
    	Utilisateur u3{"Geraldine"};
    	Utilisateur u4{"Bernard"};
    	Utilisateur u5{"Christian"};
     
    	Groupe groupe{"vacances"};
     
    	for(auto user : {&u1, &u2, &u3, &u4, &u5})
    		groupe.push_back(*user);
    }

  4. #4
    Membre averti
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2018
    Messages
    28
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 28
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2018
    Messages : 28
    Par défaut
    Tout d'abord merci à tous les deux pour vos réponses.

    Il faudrait définir "ça ne marche plus" avec un peu plus de détails...
    C'est ce que j'essayais d'expliquer, en gros, si je mets pas la référence, mon objet "groupe" ne contient qu'un seul Utilisateur correspondant au premier += qui est fait.

    Il faut bien comprendre que ce veut dire "retourner *this par référence" et "retourner *this pas par référence". Dans le premier, tu renvoies l'objet en cours (une référence sur *this). Dans le second cas, que renvoies-tu ? Si tu ne renvoies pas une référence sur l'objet en cours, tu renvoies..... une copie ! Quand tu chaines les appels par copie, et bien sur modifies, tu copies, tu modifies, tu copies, et à la fin, si tu ne remets pas tout ça dans un objet que tu gardes et bien tu perds tout...
    Alors, si je comprends bien, le premier += modifie l'objet courant et renvoie une copie de l'objet courant qui est donc créée, appelons la groupe1. C'est groupe1 qui est pris en paramètre pour += u2, là encore on retourne une copie, groupe2, qui contient u1 et u2. Ainsi de suite jusqu'à += u5 qui retourne groupe5 qui contient bien u1, u2, u3, u4 et u5.
    Ensuite, toutes les copies (groupe1, ..., groupe5) sont détruites et il ne reste que groupe qui n'a été modifié que par le premier += et qui ne contient donc que u1.

    Est ce bien ça ?

    Pour ce qui est du commentaire de Pyramidev, le main.cpp m'est donné par ma prof et je ne peux pas le modifier. Je suis d'accord sur le fait que ça ne semble pas super optimal, mais j'imagine que son objectif est de nous apprendre à utiliser certains outils.

    En outre, pour la gestion de la mémoire, il n'y a pas besoin de mettre des new partout. Le C++ est différent du Java.
    Je veux bien que tu développes un peu plus ce que tu veux dire par là, mon prof insiste beaucoup pour utilise l'allocation dynamique au maximum.

    Je ne connaissais en effet pas la range-based for loop je vais me renseigner à ce sujet, merci !

  5. #5
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut
    Citation Envoyé par iXeRay Voir le message
    Je veux bien que tu développes un peu plus ce que tu veux dire par là, mon prof insiste beaucoup pour utilise l'allocation dynamique au maximum.
    En général, quand on peut, on construit des objets dans la pile, parce que c'est plus facile à gérer : quand on sort de la portée d'un objet qui a été alloué dans la pile, cet objet est automatiquement détruit.

    Quand on utilise new, il ne faut pas oublier d'appeler delete. Les choses se compliquent quand on prend en compte un mécanisme de gestion d'erreurs qui s'appelle les exceptions, que tu verras plus tard. Concrètement, il y a beaucoup de code qui, quand il rencontre une erreur, interrompt l'exécution du bloc en cours et lance une erreur qui sera peut-être rattrapée quelque part. Or, si ce code qui lance l'erreur se trouve après ton code qui appelle new mais avant celui qui appelle delete, alors le code qui appelle delete ne sera pas appelé.
    Pour cette raison, parmi les bonnes pratiques du C++, il y en a une qui conseille d'éviter de faire des delete à la main et qui conseille d'utiliser des types comme std::unique_ptr, mais tu verras ça aussi plus tard.

    Un autre problème de l'allocation dynamique est qu'elle est moins performante que l'allocation dans la pile, car elle oblige le programme à chercher où il peut allouer dans la mémoire dynamique. Par contre, quand on alloue dans la pile, il n'y a aucune recherche à faire : le programme alloue toujours au sommet de la pile, dont il connaît l'adresse à jour en permanence.

  6. #6
    Membre averti
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2018
    Messages
    28
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 28
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2018
    Messages : 28
    Par défaut
    D'accord, je vois, je pensais justement qu'on essayait d'utiliser la pile un maximum car sa mémoire était limitée.

    Merci beaucoup pour l’explication !

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Retour par référence sur const
    Par Cheps dans le forum C++
    Réponses: 3
    Dernier message: 14/12/2008, 22h36
  2. Retour par référence d'un pointeur
    Par FunkyTech dans le forum C++
    Réponses: 16
    Dernier message: 22/07/2008, 13h56
  3. Réponses: 2
    Dernier message: 05/04/2008, 16h07
  4. retour par référence de l'opérateur ++
    Par BigNic dans le forum C++
    Réponses: 4
    Dernier message: 02/08/2006, 18h35

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo