Partage
  • Partager sur Facebook
  • Partager sur Twitter

Fonction "friend" et encapsulation

Sujet résolu
    4 janvier 2021 à 18:28:10

    Bonsoir,

    Dans un autre post (je duplique pour ne pas polluer), au sujet d'une fonction friend et de l'encapsulation :

    lmghs a écrit :

    Dedeun (avec toute sa candeur) a écrit :

    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.

    Je comprends ce que veut me dire LMGHS, ... et ça me change un peu de perspective, par rapport à l'encapsulation. Donc une fonction "friend" serait avec le même niveau de privilège qu'une fonction membre, avec le même niveau de responsabilité. Elle est donc assujettie aux contrats de la classe.

    Le risque est qu'elle ne les respecte pas ! Comment faire pour, en cas de modification d'une classe, et en particulier de ses contrats, on n'oublie pas ces fonctions "friend" ?

    Pour une classe sans fonctions "friend", l'objectif est de réduire le nombre de fonctions-menbres qui modifient les variables-membres, pour en cas de modifications profondes de l'objet (et donc de ses contrats), on réduise le travail et les tests de non reg. Dans le même sens, faut-il aussi réduire le nombre de fonction "friend" ?

    Est-ce-que ce que j'écrit à un sens ? Ou est-ce-que je m'égare ?

     Cordialement

    • Partager sur Facebook
    • Partager sur Twitter
      4 janvier 2021 à 18:48:32

      Une fonction amie appartient à l'interface immédiate d'une classe. Ce n'est pas parce que l'on écrit f(a) ou a.f() que cela change grand chose.

      Que la fonction s'appelle A::f ou ::f, cela ne change rien: il n'en existe qu'une et une seule, et son mainteneur doit être le mainteneur de A.

      Pour ne pas l'oublier, la définir dans A.cpp est un bon début. Voire directement inlinée dans la définition de A.

      Bref, en ce qui me concerne, l'amitié renforce l'encapsulation. La pire idée que l'on puisse avoir c'est des attributs publics (en présence d'invariants) ou des setters juste pour éviter d'employer le mot clé friend.

      La FAQ C++lite traite un peu plus en détail du sujet: https://isocpp.org/wiki/faq/friends#friends-and-encap

      > Many people think of a friend function as something outside the class. Instead, try thinking of a friend function as part of the class’s public interface. A friend function in the class declaration doesn’t violate encapsulation any more than a public member function violates encapsulation: both have exactly the same authority with respect to accessing the class’s non-public parts.

      Il ne faut pas perdre de vu que ce n'est pas la fonction qui se déclare amie sans demander notre avis! C'est nous qui explicitons les fonctions auxquelles nous donnons un accès complet (membres et amies). Leur liste est très précisément connue dans la définition de la classe, et elle est de taille finie.

      -
      Edité par lmghs 4 janvier 2021 à 18:49:39

      • 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 à 19:15:55

        lmghs a écrit:

        Bref, en ce qui me concerne, l'amitié renforce l'encapsulation. La pire idée que l'on puisse avoir c'est des attributs publics (en présence d'invariants) ou des setters juste pour éviter d'employer le mot clé friend.

        Il faut avouer qu'avoir des fonctions membre "estEgal" ou "afficher", comme on voit dans le cours, juste pour éviter d'avoir friend avec les opérateurs, ca ne renforce pas du tout l'encapsulation et ça dégueulasse inutilement l'interface de la classe.

        Dedeun a écrit:

        Pour une classe sans fonctions "friend", l'objectif est de réduire le nombre de fonctions-menbres qui modifient les variables-membres, pour en cas de modifications profondes de l'objet (et donc de ses contrats), on réduise le travail et les tests de non reg. Dans le même sens, faut-il aussi réduire le nombre de fonction "friend" ?


        De toute facon, a partir du moment où tu casses les contrats d'une classe, tu prends le risque d'avoir des oublis et des erreurs. Les principes SOLID visent justement à éviter d'avoir à casser les contrats (OCP), a limiter le nombre de classes qui seront impactées par le changement (Demeter, LSP, etc) et à réduire les dégâts dans la classes (SRP).

        Et ensuite, il faut limiter le risque de ne pas voir une fonction a changer, en respectant une organisation physique du code (ie ne pas mettre le code des fonctions friend dans un fichier a l'autre bout du monde).

        Apres, si les tests sont bien écrits (mais qui fait ca ? :D ), alors un problème dans les fonctions friend devrait etre détecté. Avec un peu de chance...

        • Partager sur Facebook
        • Partager sur Twitter
          5 janvier 2021 à 22:20:00

          Bonsoir,

          Merci pour vos réponses,

          Depuis ce matin que j'ai lu vos réponses, je cherche un exemple de modification d'un objet pour des raisons classiques : optimisation, clarification, simplification, ... ("refactoring", comme ils disent) qui vont conduire à des modifications de contrats ... et j'en ai pas trouvé. En effet, il n'y a pas besoin de modifier les interfaces (donc les préconditions, les post-conditions ou les invariants) si l'on fait varier la représentation interne et les process internes. En fait c'est ce à quoi je pensais, avec ce sujet.

          Donc, je ferme ce sujet, faute d'argument.

          Merci pour les idées, ... et pour cette nouvelle vision.

          Cordialement

          • Partager sur Facebook
          • Partager sur Twitter
            6 janvier 2021 à 9:30:51

            Salut,

            En fait, l'amitié est comme beaucoup d'autres techniques: c'est leur abus qui fini par nuire

            Définir les différents opérateurs dont tu as besoin comme étant des amis de ta classe va -- très certainement -- améliorer l'encapsulation, en t'évitant d'avoir à fournir des accesseurs, des mutateurs ou autres fonctions qui n'ont "rien à foutre là".

            Ce qui va poser problème, c'est si tu déclare une fonction ou une classe comme étant amie d'une autre alors que cette fonction (ou cette classe) devrait en réalité s'appuyer sur l'interface "normale" de la classe; et, pire encore, lorsque que tu commence à multiplier les amitiés qui n'ont "aucune raison d'être".

            Comme j'ai bien conscience d'avoir fait une réponse de normand, je vais présenter un exemple pour chaque cas:

            Les bonnes raisons de déclarer une fonction amie:

            les opérateurs de flux ( std::ostream & operator << (std::ostream & , MaClasse const &) et std::istream & opearor>>(std::istream &, MaClasse &) ) ont de très bonnes raisons d'être déclarés amis de la classe, car le premier est destiné à "exporter" l'état réel de la classe (en vue d'une récupération ultérieure) et le deuxième a, justement, pour but de récupérer un "état sauvegardé" afin de permettre la création d'une instance "dans un état (cohérent) qui a existé".

            Il faudra, bien sur, s'interroger sur l'éventuelle capacité à définir une instance dans un "état incohérent" de la classe pour l'opérateur >>,  cependant, étant donné que ces opérateurs sont destinés à être utilisés dans des circonstances très particulières, l'amitié va permettre d'améliorer l'encapsulation générale de la classe.

            Les mauvaises raisons de déclarer une classe ou une fonction amie

            Par contre, les mauvaises raisons de déclarer une classe ou une fonction amie, ce serait, par exemple, de déclarer la classe Vehicule amie de la classe Reservoir, sous prétexte que c'est la classe  Vehicule qui doit -- en toute logique -- modifier la quantité de carburant que le réservoir contient.

            Dans ce cas, il faut que la classe Vehicule utilise l'interface "normale" de la classe Reservoir, car tout ce que l'instance de véhicule pourra faire à ce sujet est -- forcément -- soumis à l'ensemble des invariants, pré et post conditions qui sont imposés au réservoir

            • Partager sur Facebook
            • Partager sur Twitter
            Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
              6 janvier 2021 à 12:19:15

              Dans les bonnes raisons, il y a aussi les hidden friends qui limitent les recherches de noms en cas de messages d'erreur (situation directement visible et parfaite pour le marketing des hidden-friends). Exemple: https://gcc.godbolt.org/z/j8z7s1

              Dans le cas 'a - a', le compilo parle qu'il a trouvé une surcharge pour B. Dans le cas 'b + b', le compilo ne parle pas de la surcharge qui existe pour A. Grace au friend!

              • 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.
                6 janvier 2021 à 13:02:54

                En fait, le problème c'est que C++ pose au départ la classe comme étant la frontière de l'API d'un "composant logiciel".

                La notion d'amis est là pour rectifier le tir, elle autorise certains "partenaires" identifiés à utiliser les détails internes (privés) d'une classe.

                La question a été mieux traitée en Java, où il y a la notion de package, composé de classes qui "vont ensemble".

                • Partager sur Facebook
                • Partager sur Twitter
                  6 janvier 2021 à 21:58:17

                  Bonsoir, et merci pour ces nouvelles réponses

                  koala01 a écrit :

                  En fait, l'amitié est comme beaucoup d'autres techniques: c'est leur abus qui fini par nuire

                  Ok, et Ok aussi pour la suite de ton post.

                  lmghs a écrit :

                  Dans les bonnes raisons, il y a aussi les hidden friends qui limitent les recherches de noms en cas de messages d'erreur  ...

                  Ok, mais est-ce-que ça se limite aux erreurs ? Ou alors ça optimise le travail du compilateur ? Ça fait ou empêche des erreurs de cas "aux bornes" lors de la compilation ?

                  michelbillaud a écrit :

                  ... elle autorise certains "partenaires" identifiés à utiliser les détails internes (privés) d'une classe.

                  Ok et merci Michel (mais je connais pas Java !)

                   Cordialement

                  • Partager sur Facebook
                  • Partager sur Twitter
                    6 janvier 2021 à 23:17:49

                    Dedeun a écrit:

                    lmghs a écrit :

                    Dans les bonnes raisons, il y a aussi les hidden friends qui limitent les recherches de noms en cas de messages d'erreur  ...

                    Ok, mais est-ce-que ça se limite aux erreurs ? Ou alors ça optimise le travail du compilateur ? Ça fait ou empêche des erreurs de cas "aux bornes" lors de la compilation ?

                    Cela a également un intérêt pour réduire les cas de conversions implicites pas forcément bien venues (on peut réduire les conversions implicites avec des opérateurs de conversion explicites)

                    D'après l'article d'Anthony Williams, https://www.justsoftwaresolutions.co.uk/cplusplus/hidden-friends.html, il y aurait moyen que le compilateur compile plus vite en réduisant la quantité de cas considérés sur un simple `a == b`. Pourquoi pas.

                    Concernant Java, je ne sais pas jusqu'à quel point on peut comparer ici dans la mesure où l'on n'a pas de fonctions libres en Java. Peut-être si la notion de package supportait des fonctions libres? Je n'ai pas creusé l’hypothèse.

                    -
                    Edité par lmghs 6 janvier 2021 à 23:20:07

                    • 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.
                      7 janvier 2021 à 0:20:43

                      Fonctions libres en java : méthodes statiques, qu'on colle dans un package machin.truc.util, et hop.

                      Exemple, la fonction sinus

                      public static double sin(double a)


                      qui est déclarée dans la classe Math du package java.lang

                      -
                      Edité par michelbillaud 8 janvier 2021 à 8:12:55

                      • Partager sur Facebook
                      • Partager sur Twitter
                        8 janvier 2021 à 7:46:32

                        Bonjour,

                        Merci pour vos réponses.

                        Cordialement.

                        • Partager sur Facebook
                        • Partager sur Twitter

                        Fonction "friend" et encapsulation

                        × 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