Partage
  • Partager sur Facebook
  • Partager sur Twitter

Surchargez un opérateur

Sujet résolu
    3 janvier 2021 à 17:41:59

    Bonjour à tous ! 

    Cela fait un petit moment maintenant que je suis bloqué au chapitre nommé "Surchargez un opérateur". Le problème est que je n'arrive pas à comprendre comme fonction le operator==. A chaque fois que je mets le prototype "bool operator==(Duree const& a, Duree const& b);", une erreur m'indique que 'operator==' doit renvoyer une valeur. Aussi, je n'ai que le prototype mais pas de fonction, car je ne sais pas quoi mettre à l'intérieur de cette fonction. Pour le moment, voici mon fichier d'en-tête pour ma classe Durée. 

    #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;
    };
    
    bool operator==(Duree const& a, Duree const& b);
    
    #endif

    Je ne sais pas si ce que je fais est bon ou non, je ne sais pas comment remédier au problème car le cours sur cette partie est trop flou. 

    Merci d'avance pour votre aide ^^

    • Partager sur Facebook
    • Partager sur Twitter
      3 janvier 2021 à 18:08:07

      Bonjour,

      Si je comprends bien, tu as une classe Duree, avec un constructeur, et tu souhaites réaliser l'opérateur == entre deux durées. Ta déclaration me semble correct, il faut maintenant que tu écrives le corps de ta fonction.

      À quelle condition tes deux durées sont elle égales ?

      Comme tu as le bon prototype, le corps de ta fonction sera comme suit :

      bool operator==(Duree const& a, Duree const& b)
      {
          if (?){
              retrun (true);
           } else {
              return (false);
           } // end if
      }

      Je te laisse chercher ce que doit être (?). Revient vers nous si tu ne trouve pas.

      Cordialement.

      PS on peut optimiser avec:

      bool operator==(Duree const& a, Duree const& b)
      {
           retrun (?);
      }

      Mais c'est pas aussi clair pour l'explication.

      -
      Edité par Dedeun 3 janvier 2021 à 18:14:12

      • Partager sur Facebook
      • Partager sur Twitter
        3 janvier 2021 à 18:22:21

        J'ai essayé de faire ce que tu m'as dit, donc le corps de la fonction que voici : 
        bool operator==(Duree const& a, Duree const& b)
        {
        	if (a == b)
        	{
        		return (true);
        	}
        	else
        	{
        		return (false);
        	}
        }

        mais le problème reste le même, c'est à dire que l'erreur est toujours 'operator==' doit retourner une valeur. 

        PS : J'ai mis le corps de la fonction dans le fichier cpp de la classe. Je ne sais pas s'il y a un rapport ou non.

        • Partager sur Facebook
        • Partager sur Twitter
          3 janvier 2021 à 18:37:46

          Ho la la ! Il y a un problème ! "C'est le serpent qui se mord la queue" :)

          Tu es en train de définir l'opération == entre deux durées, et pour le faire tu utilises ... l'opération == entre deux durées ! Dans ton if (a==b), tu essais d'utiliser le résulta de ce que tu écris !

          Non, c'est pas possible !

          L'opérateur == n'est définit que pour les types simples, pas pour les classe que tu conçois. D'ailleurs, c'est ce que tu essais de faire, définir cet opérateur entre deux "durée".

          Pour savoir si deux durées sont équivalente, il va falloir que tu regardes (et compare) tous les membres de ta classe : Y a-t-il le même nombre d'heures, de minute ou de seconde ?

          Cordialement.

          -
          Edité par Dedeun 3 janvier 2021 à 18:42:17

          • Partager sur Facebook
          • Partager sur Twitter
            4 janvier 2021 à 10:05:59

            Ah d'accord, très bien, je pensais que c'était possible vu que c'est une opération de base mais vu que c'est pour une classe ça ne marchera pas, effectivement.

            Maintenant ce que je veux faire, c'est de prendre mes attributs et de les comparés entre eux, mais ce n'est pas possible sans mettre mettre les attributs en public. Du coup, je ne trouve pas d'autres moyens... 

            bool operator==(Duree const& a, Duree const& b)
            {
            	if (a.m_heures == b.m_heures && a.m_minutes == b.m_minutes && a.m_secondes == b.m_secondes)
            	{
            		return true;
            	}
            	else
            	{
            		return false;
            	}
            }

            Et pour optimiser, il faut pas mettre : 

            bool operator==(Duree const& a, Duree const& b)
            {
            	return (a.m_heures == b.m_heures && a.m_minutes == b.m_minutes && a.m_secondes == b.m_secondes)
            }

            ?

            • Partager sur Facebook
            • Partager sur Twitter
              4 janvier 2021 à 11:22:27

              @Limword, oui c'est ça qu'il faut faire.

              Attention, il manque un point-virgule dans la version optimisée.

              De plus tes champs m_heures m_minutes m_secondes sont private, et il faut que ton opérateur d'égalité puisse accéder à ces champs. Pour cela tu peux par exemple dire que l'opérateur est un "ami" en écrivant dans la class Duree :

                   friend bool operator==(Duree const&, Duree const&);
              • Partager sur Facebook
              • Partager sur Twitter

              En recherche d'emploi.

                4 janvier 2021 à 11:29:33

                Effectivement, il manquait un point-virgule! 

                Merci pour vos réponses, j'ai maintenant compris comme il fallait faire, mais j'aurais tout de même une dernière question. Quel est le plus optimisé entre utiliser le "friend" et faire un attribut "estEgal" qui revient à faire 'operator==' ? 

                • Partager sur Facebook
                • Partager sur Twitter
                  4 janvier 2021 à 11:54:10

                  L'optimisation est le boulot du compilateur.
                  Utiliser friend ne crée aucun code donc est forcément optimal, mais si on crée une fonction supplémentaire c'est l'optimiseur qui fera que la fonction en plus n'ajoute aucun surcoût, du moins si elle est déclarée inline.
                  • Partager sur Facebook
                  • Partager sur Twitter

                  En recherche d'emploi.

                    4 janvier 2021 à 12:14:27

                    inline ne sert pas à optimiser du code. C'est avant tout une directive de link pour autoriser les multiple-définitions. Le compilateur est libre de ne pas inliner une fonction inline, comme il peut le faire même en absence de ce mot clef.

                    • Partager sur Facebook
                    • Partager sur Twitter
                      4 janvier 2021 à 12:23:47

                      Pas forcément besoin que cela soit déclaré inline pour que le compilateur inline l'appel (si les deux définitions sont dans la même unité de traduction ~~> même .cpp).

                      ((Par contre friend a ici un intérêt indirect (et globalement éloigné de son objectif initial et enseigné): celui de cacher l'existence de l'opérateur lors des tentatives de résolution de noms sur d'autres types. Même si on n'a pas besoin d'un accès privilégié, il faut considérer qu'employer friend est LA bonne pratique pour les opérateurs libres. (mot clé pour vos moteurs de recherche: "hidden friend C++". ). Je ne range pas cet aspect tordu dans les sujets pour débutants.))

                      Dans tous les cas, il n'y a aucun intérêt à avoir une fonction supplémentaire en C++. C'est beaucoup les devs qui ont du mal à s'adapter au dialecte du C++ qui s'enquiquineront à rajouter de telles fonctions redondantes -- ou parfois pour des raisons de progression pédagogique, mais je ne suis pas s^ur de son bien fondé faute d'"expérimentation" sur le sujet. Un peu comme les fonctions "init" alors que l'on a des constructeurs.

                      NB: il reste des cas où il reste nécessaire d'avoir de telles fonctions pour lever des ambiguïtés. P.ex.: quelle est la multiplication entre deux vecteurs? Produit scalaire? Produit Vectoriel? Produit membre à membre?

                      • Partager sur Facebook
                      • Partager sur Twitter
                      C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                        4 janvier 2021 à 14:05:21

                        Bonjour LimWord,

                        Limword a écrit:

                        Maintenant ce que je veux faire, c'est de prendre mes attributs et de les comparés entre eux, mais ce n'est pas possible sans mettre mettre les attributs en public. Du coup, je ne trouve pas d'autres moyens...

                        Ok, c'est là que je voulais t'amener ! Tu as trouvé la première solution : Mettre les variables-membres en publique, c'est une première solution.

                        Dalfab te donne une seconde solution :

                        Dalfab a écrit:

                        Pour cela tu peux par exemple dire que l'opérateur est un "ami" en écrivant dans la class Duree:

                             friend bool operator==(Duree const&, Duree const&);

                         Tu as trouvé une 3 eme solution:

                        Limword a écrit:

                        Quel est le plus optimisé entre utiliser le "friend" et faire un attribut "estEgal" qui revient à faire 'operator==' ? 

                        c'est à dire faire une fonction membre bool EstEgal (dure const & d) et appeler cette fonction membre dans ton "operator=="

                        bool operator==(Duree const& a, Duree const& b)
                        {
                            return (a.estEgal(b))
                        }

                        Mais, il y a aussi d'autres solutions (avec des avantages et des inconvégnants). Par exemple, tu converties tes durée en secondes, et tu compare le nombre de secondes :

                        #ifndef DEF_DUREE
                        #define DEF_DUREE
                         
                        class Duree
                        {
                        public:
                         
                            Duree(int heures = 0, int minutes = 0, int secondes = 0)
                            {
                                m_sec = (heure*3600)+(minutes*60)+secondes;
                            };
                            bool estEgal (Dure const & d) const
                            {
                                return (m_sec == d.m_sec);
                            }; 
                        private:
                            long m_sec;
                        };
                         
                        bool operator==(Duree const& a, Duree const& b)
                        {
                             return (a.estEgal(b));
                         
                        #endif

                        Et il y a sûrement encore d'autres possibilités ... Quelle est la meilleure ? (Quand j'avais dit "optimisation" dans un post précédent, je voulais dire "optimisation du nombre de libre de code à écrire" ou "clareté du code", je ne parlais pas de vitesse d'éxécussion).

                        La meilleure solution va dépendre de ce que tu veux faire de cette classe Durée:

                        • Si elle doit faire des calcules (faire des additions/soustractions/multiplication sur des durées) ma dernière proposition est peut-être la meilleurs.
                        • Si on ne doit que afficher des durées, ce n'est peut-être pas la peinne.

                        Tout dépend de quel service ta classe doit rendre.

                        Pour ma part, il me semble que ta premiere solution n'est pas la bonne: En effet, tu as surement des contraintes sur m_heures, m_minutes et m_secondes (0 <= m_minute < 60 et 0 <= m_secondes < 60) : tu as un contrat sur ta classe ! Si tu mets en publique les variables-membres, tu ne pourras pas assurer ce contrat. Pour pouvoir l'assurer il vaut être obligé de passer par le constructeur ou un "seter" pour modifier ces variables-membres, donc de les laisser en private.

                        La solution d'utiliser un "friend" est une solution un peu intérmédiaire qui "casse en maitrisant quand même" l'acces hors classe de variables-membre, ... ca peut être à risque. (LMGHS pense lui que c'est une bonne solution, voir son post précédent)

                        En ce qui concerne le "inline", je laisse des gens plus calés que moi en discuter.

                        J'espère avoir répondu à tes questions, et ouvert d'autre horizons.

                        Cordialement

                        -
                        Edité par Dedeun 4 janvier 2021 à 14:11:50

                        • Partager sur Facebook
                        • Partager sur Twitter
                          4 janvier 2021 à 14:42:21

                          D'accord, merci beaucoup à vous tous pour vos réponses :D.

                          Pour ce qui concerne le "inline", je ne comprends absolument rien mais ça doit pas être grave pour le moment !

                          • Partager sur Facebook
                          • Partager sur Twitter
                            4 janvier 2021 à 14:47:56

                            > La solution d'utiliser un "friend" est une solution un peu intérmédiaire qui "casse en maitrisant quand même" l'acces hors classe de variables-membre, ... ca peut être à risque. (LMGHS pense lui que c'est une bonne solution, voir son post précédent)

                            Ca ne casse rien du tout. C'est une fonction tout autant privilégiée qu'une fonction membre sauf que techniquement elle est libre (i.e. non-membre). Elle appartient toujours à l'interface de la classe. Nous en sommes toujours responsables, tout comme nous sommes responsables des fonctions membres.

                            • Partager sur Facebook
                            • Partager sur Twitter
                            C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                              4 janvier 2021 à 17:59:16

                              Bonsoir,

                              Limword a écrit:

                              Pour ce qui concerne le "inline", je ne comprends absolument rien mais ça doit pas être grave pour le moment !

                              En effet, c'est pas grave. Tu peux oublier pour l'instant. (Cette directive, quand elle est bien utilisée, demande au compilateur de transformer la procédure "normal" en procédure "Inline", c’est-à-dire qu’au lieu de l'appeler (avec changement de pointeur de pile et de PC) il duplique le code dans l'appelant. Le code de cette fonction "Inline" sera dupliqué autant de fois qu'elle est appelée ! Ca grossit un peu le code, mais c'est un peu plus rapide. Aussi, quand on parle "Optimisation [de la durée d'exécution]" aussitôt on pense à "Inline". Bien sûr, comme te le font remarquer Jo_link_noir et LMGHS, le compilateur est plus malin que toi, et il ne respectera pas forcément ce que tu lui as demandé, parfois il fait sans que tu demandes, parfois il fait pas alors que tu lui as demandé ! De toute manière, c'est transparent, "Inline" ou pas, ça donne le même résultat !)

                              J'espère que nous avons répondu à tes questions, si non, reformule les, si oui, n'oublie pas de clore le sujet (en haut).

                              Cordialement

                              • Partager sur Facebook
                              • Partager sur Twitter
                                4 janvier 2021 à 18:45:44

                                lmghs a écrit:

                                ((Par contre friend a ici un intérêt indirect (et globalement éloigné de son objectif initial et enseigné): celui de cacher l'existence de l'opérateur lors des tentatives de résolution de noms sur d'autres types. Même si on n'a pas besoin d'un accès privilégié, il faut considérer qu'employer friend est LA bonne pratique pour les opérateurs libres. (mot clé pour vos moteurs de recherche: "hidden friend C++". ). Je ne range pas cet aspect tordu dans les sujets pour débutants.))

                                On voit dans cette discussion qu'il y a plusieurs façons d'implémenter les opérateurs et savoir quelle est la "bonne" façon est clairement hors de portée des débutants. Voire même pour des devs C++ intermédiaires.

                                Pour les débutants, il faut à mon sens expliquer qu'il y a des écritures idiomatiques (c'est a dire "reconnus comme étant la bonne façon de faire dans un langage") et ne pas entrer plus loin dans les explications. Pour la surcharge d'opérateurs, cf la doc : https://en.cppreference.com/w/cpp/language/operators 

                                • Partager sur Facebook
                                • Partager sur Twitter
                                  5 janvier 2021 à 9:07:46

                                  J'ai regardé l'exemple en bas de la doc qu'a link gbdivers, je le remet ici :

                                  #include <iostream>
                                   
                                  class Fraction
                                  {
                                      int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
                                      int n, d;
                                  public:
                                      Fraction(int n, int d = 1) : n(n/gcd(n, d)), d(d/gcd(n, d)) { }
                                      int num() const { return n; }
                                      int den() const { return d; }
                                      Fraction& operator*=(const Fraction& rhs)
                                      {
                                          int new_n = n * rhs.n/gcd(n * rhs.n, d * rhs.d);
                                          d = d * rhs.d/gcd(n * rhs.n, d * rhs.d);
                                          n = new_n;
                                          return *this;
                                      }
                                  };
                                  std::ostream& operator<<(std::ostream& out, const Fraction& f)
                                  {
                                     return out << f.num() << '/' << f.den() ;
                                  }
                                  bool operator==(const Fraction& lhs, const Fraction& rhs)
                                  {
                                      return lhs.num() == rhs.num() && lhs.den() == rhs.den();
                                  }
                                  bool operator!=(const Fraction& lhs, const Fraction& rhs)
                                  {
                                      return !(lhs == rhs);
                                  }
                                  Fraction operator*(Fraction lhs, const Fraction& rhs)
                                  {
                                      return lhs *= rhs;
                                  }
                                   
                                  int main()
                                  {
                                     Fraction f1(3, 8), f2(1, 2), f3(10, 2);
                                     std::cout << f1 << " * " << f2 << " = " << f1 * f2 << '\n'
                                               << f2 << " * " << f3 << " = " << f2 * f3 << '\n'
                                               <<  2 << " * " << f1 << " = " <<  2 * f1 << '\n';
                                  }

                                  Et je suis plutôt étonné. En effet cette classe Fraction fournit un accesseur public sur ses deux variables membres n et d qui sont utilisés dans la définition des opérateurs. Opérateurs qui sont ici des fonctions libres.

                                  Ca m'étonne parce que j'ai lu énormément de post ici (et soutenus par les intervenants de ce post), sur stackoverflow ou divers blog sur "why setters and getters are evil". Alors ok, un accesseur sur le numérateur et le dénominateur d'une classe fraction peut-être un service qu'on attend de cette classe. Dans le cas particulier de la calsse Fraction pourquoi pas, cette implémentation me choque pas tant que ça 

                                  Mais j'ai un peu du mal à croire que fournir à un accesseur sur chaque variable membre dont on aurait besoin dans la définition de nos opérateurs libres soit "l'écriture idiomatique reconnu comme étant la bonne façon de faire".

                                  Mais du coup, c'est vraiment ça la bonne méthode ? Ou c'est autre chose ? Moi j'aurais dit que la bonne méthode c'est que l'opérateur soit une fonction libre amie mais je dis ça juste parce que c'est comme ça que j'ai vu les opérateurs être définis la plupart du temps (y compris dans c++ primer que j'ai récemment commencé mais j'en suis qu'au début donc peut-être qu'il fait autrement et justifie tel ou tel méthode plus loin), je suis incapable d'argumenter sur le bien fondé ou non de cette idée reçue que j'ai 

                                  Ce que je fais habituellement :

                                  - opérateurs +=, -=, *= , .... ==> fonctions membres qui modifient directement l'objet comme ceci :

                                  Fraction& operator*=(const Fraction& rhs)
                                      {
                                          int new_n = n * rhs.n/gcd(n * rhs.n, d * rhs.d);
                                          d = d * rhs.d/gcd(n * rhs.n, d * rhs.d);
                                          n = new_n;
                                          return *this;
                                      }

                                  - opérateurs +, -, *, .... ==> fonctions libres qui utilisent l'opérateur +=, ... avec premier paramètre passé par valeur et le second par référence constante comme ceci : 

                                  Fraction operator*(Fraction lhs, const Fraction& rhs)
                                  {
                                      return lhs *= rhs;
                                  }

                                  - Tous les autres opérateurs <<, ==, <, >, .... déclarée comme fonction friend dans la classe et définie en dehors de la classe. Dans le cas de la classe Fraction ça donnerait

                                  class Fraction
                                  {
                                      int n, d;
                                  public:
                                      friend bool operator==(const Fraction& lhs, const Fraction& rhs);
                                      Fraction(int n, int d = 1) : n(n/gcd(n, d)), d(d/gcd(n, d)) { }
                                  };
                                  
                                  bool operator==(const Fraction& lhs, const Fraction& rhs)
                                  {
                                      return(lhs.n == rhs.n && lhs.d == rhs.d);
                                  }

                                  Est ce que c'est la bonne méthode pour surcharger les opérateurs ? Ou au moins une des bonnes méthodes svp ?

                                  Merci d'avance


                                  -
                                  Edité par ThibaultVnt 5 janvier 2021 à 9:14:50

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    5 janvier 2021 à 13:55:28

                                    > Mais j'ai un peu du mal à croire que fournir à un accesseur sur chaque variable membre dont on aurait besoin dans la définition de nos opérateurs libres soit "l'écriture idiomatique reconnu comme étant la bonne façon de faire".

                                    Ici effectivement, ouvrir l'accès en lecture aux champs n et d a du sens, cela fait parti du service.

                                    Ensuite, que l'on implémente l'opérateur à partir de n ou num() ne fait pas trop de différence de mon point de vue. A la limite, num() faisant parti de l'interface publique a moins de chances de changer. Un `m_numerator`  pourrait tout à fait se voir imposé par une règle qualité.

                                    Sinon, tous les opérateurs libres mériteraient d’être amis, pour les raisons évoquées plus tôt. Et à partir du prochain standard seul `<=>` serait idéalement implémenté.

                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                    C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                                      5 janvier 2021 à 17:16:39

                                      ThibaultVnt a écrit:

                                      Mais j'ai un peu du mal à croire que fournir à un accesseur sur chaque variable membre dont on aurait besoin dans la définition de nos opérateurs libres soit "l'écriture idiomatique reconnu comme étant la bonne façon de faire".

                                      Oui, c'est un cas particulier du Fraction. Ce sont les explications données avant qu'il faut suivre.

                                      Je suis surpris qu'ils n'ont pas suivi leur propre recommandation pour operator*, qui n'est pas dans la classe et friend. Peut qu'il faudrait proposer un auter code d'exemple sur cppreference, qui soit moins ambigue, sans getter et avec friend.

                                      • Partager sur Facebook
                                      • Partager sur Twitter

                                      Surchargez un opérateur

                                      × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                      × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                      • Editeur
                                      • Markdown