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 :

Pour ou contre l'amitié entre classes


Sujet :

C++

  1. #1
    Membre expert
    Pour ou contre l'amitié entre classes
    Ceci est un fork de la discussion héritage et classes permettant à chacun de donner son avis sur le concept d'amitié

    Citation Envoyé par Aspic Voir le message
    Question : Dans la classe ZoneDonjon, j'ai besoin d'accéder à l'attribue "nbCle" qui se trouve dans la classe Donjon. Comment faire intelligemment, sachant que j'ai besoin de modifier cet attribue dans la classe ZoneDonjon ?
    Il y a plusieurs manières.
    Celle qui a ma préférence, et qui est la plus "classique" : tu ajoutes un getter et un setter (méthodes publiques getNbCle() et setNbCle(int)) dans la classe Donjon. Ca te donne un peu de contrôle sur les accès et les modifications, si tu as des tests à faire pour vérifier que les modifs sont cohérentes par exemple.

    Sinon, tu peux déclarer la classe ZoneDonjon ou certaines de ses méthodes comme "friend" de Donjon, mais à mon sens c'est une brèche dans la "sécurité" objet en C++.

    Sinon, encore pire à mon sens, tu repasses nbCle en public, c'est-à-dire que tu renonces tout-à-fait aux fonctionnalités de contrôle d'accès pour cette variable membre.

  2. #2
    Expert éminent sénior
    Salut,

    Citation Envoyé par Eusebius Voir le message

    Sinon, tu peux déclarer la classe ZoneDonjon ou certaines de ses méthodes comme "friend" de Donjon, mais à mon sens c'est une brèche dans la "sécurité" objet en C++.
    Appliquée correctement et avec parcimonie, l'amitié permet, au contraire, d'améliorer la sécurité objet en C++...

    Cf la FAQ: l'amitié brise-t-elle l'encapsulation

    Citation Envoyé par Aspic Voir le message
    Je suis peut être bête mais aucune de ses solutions ne va marcher...
    Si par exemple, je le passe en public, comment j'y accède dans ma classe ZoneDonjon, vu qu'il n'y a pas de liens avec la classe Donjon ?
    A vrai dire, j'ai un peu de mal à comprendre ton raisonnement...

    Si j'ai bien compris, le role de la zone est de gérer tout ou partie d'un monde (typeMonde), et donc, le role de la zoneDonjon et... de gérer spécifiquement tout ou partie d'un "monde donjon", non

    Il doit donc y avoir, d'une manière ou d'une autre, une relation entre tes zones (quelles qu'elles soient) et le monde qu'elles doivent gérer

    Maintenant, il serait, éventuellement, possible d'envisager l'utilisation d'un DP proche du médiateur: Tes objets de type Zone (ou dérivés) s'adresse à un médiateur qui transmet, quand il le faut, les instructions à l'objet de type Monde (ou dérivé) courent.

    Mais, quoi qu'il en soit, tes Zones devront de toutes manières être en relation avec quelque chose
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Membre expert
    Citation Envoyé par koala01 Voir le message
    Salut,

    Appliquée correctement et avec parcimonie, l'amitié permet, au contraire, d'améliorer la sécurité objet en C++...
    Ici on parle d'utiliser "friend" pour contourner un problème de conception en amont, on n'est pas du tout -à mon avis- dans le cadre du cas très particulier cité par cet article de la FAQ (avec laquelle je pense que je suis en désaccord de toute manière, mais c'est un autre problème, hors-sujet).

  4. #4
    Expert éminent sénior
    Citation Envoyé par Eusebius Voir le message
    Ici on parle d'utiliser "friend" pour contourner un problème de conception en amont, on n'est pas du tout -à mon avis- dans le cadre du cas très particulier cité par cet article de la FAQ (avec laquelle je pense que je suis en désaccord de toute manière, mais c'est un autre problème, hors-sujet).
    Le fait est que, si tu place un accesseur et, surtout, un mutateur sur un membre de ta classe, tu permet à "n'importe qui" de les utiliser.

    C'est peut être opportun, mais, dans bien des cas, cela viole purement et simplement la loi Demeter qui t'impose de n'exposer que le stricte minimum nécessaire.

    Appliquée en connaissance de cause et avec parcimonie, l'amitié te permet de respecter Demeter en n'exposant pas plus que le stricte nécessaire tout en autorisant un type (ou une fonction) bien particulier(ère) à accéder aux détails d'implémentation d'un autre, même s'il est important de définir clairement le cadre dans lequel cette autorisation est donnée.

    Il y a peut-être un défaut de conception sous-jascent, je n'en disconviens pas forcément. Mais nous n'en savons pas assez sur le projet de Aspic pour nous exprimer sur le sujet
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  5. #5
    Membre expert
    Alors le message suivant (récupéré d'un MP) est rajouté à la demande expresse de koala1, visiblement désireux de me basher en public

    De plus, je ne suis pas un spécialiste de C++.
    Cependant :

    - je suis persuadé (EDIT: en d'autres termes, ça relève ici de l'intuition) qu'il est possible de mettre en place un contrôle d'accès relativement fin dans un getter/setter ;

    - déclarer un friend, c'est, en gros, sortir le contrôle de la classe à qui il correspond. Si tu déclares une méthode ou une classe comme friend, que le développement de ladite méthode ou classe est confié à une autre équipe qui trouve que ça serait bien pratique si ta variable privée était quand même publique, elle a le pouvoir de la rendre publique "de fait" en implémentant un getter et un setter d'interface. Et toi qui trouvais que les getter et les setter n'étaient pas assez sécurisés, tu te retrouves avec un getter et un setter dont tu ne maîtrises même pas le code (dont tu ne connais même pas l'existence, d'ailleurs) alors que tu aurais sans doute pu faire mieux. En résumé, c'est une considération assez théorique, mais si l'enceinte "à sécuriser" est ta classe de départ (le reste de l'application en étant a priori exclu), alors un "friend" constitue en théorie une faille dans les processus de contrôle d'accès via les mots-clés, puisqu'il permet à une classe (et donc théoriquement à toutes les autres) de les contourner.

  6. #6
    Expert confirmé
    friend est plus fin que ça. En conception, il est utilisé pour permettre à une classe ou une fonction amie de voir ou modifier l'invariant de la classe de base. Il doit être utilisé avec parcimonie, mais c'est une opération tout à fait légitime. L'effet demandé est d'offrir un meilleur contrôle sur l'accès à certaines informations à partir de l'extérieur. En particulier, friend est souvent utiliser pour donner à une classe (ou une fonction) l'accès à une API privée - plutôt que de laisser cette API en public et dire dans la doc "do not use, plz, or you'll be pwnd quickly".

    Je donne un exemple, tiré d'un test d'architecture pour un wrapper DirectX:

    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
     
    class resource
    {
    private:
      bool tie(IDirect3DDevice9* device);
      bool untie();
     
      friend class device;
    };
     
    class device
    {
    private:
      resource_collection resources;
      IDirect3DDevice9* hdevice;
     
    public:
      bool register(resource* r)
      {
        if (r->tie(hdevice)) { resources.add(r); }
      }
    private:
      bool reset_resources()
      {
        try
        {
          resources.foreach([]() { if (!r->untie()) throw resource_exception(); });
          resources.foreach([hdevice]() 
            { if (!r->tie(hdevice)) throw resource_exception(); }
          );
        }
        catch (resource_exception e)
        { return false; }
     
        return true;
      }
    };


    (désolé pour les lambda; j'essaie de simplifier le code . Le code réel n'utilise pas de lambda, mais je ne voulais pas trop alourdir la déclaration de la collection resources, son utilisation. Pour des informations sur les lambda, cf cet article.).

    En utilisant le mot-clef, plutôt que de casse l'encapsulation, j'ai un meilleur contrôle sur celle-ci. tie() et untie() ne peuvent être appelées qu'à partir de la classe device. has_reseted() et reset_resources() ne peut être appelé qu'à partir de la classe renderer. Je ne permet pas à l'utilisateur de mon API d'utiliser ces fonctions.

    Pour information, l'idée de base de ce test était

    1) de créer des classes en obéissant au principe de responsabilité unique (et ce n'est pas au device de faire le rendu d'une scène)

    2) de ne pas autoriser l'accès à quoi que ce soit de DX9 (wrapper parfait; une application complète du DP facade, en somme).
    Cette signature n'a pas pu être affichée car elle comporte des erreurs.

  7. #7
    Membre expert
    Citation Envoyé par Emmanuel Deloget Voir le message

    En utilisant le mot-clef, plutôt que de casse l'encapsulation, j'ai un meilleur contrôle sur celle-ci. tie() et untie() ne peuvent être appelées qu'à partir de la classe device. has_reseted() et reset_resources() ne peut être appelé qu'à partir de la classe renderer. Je ne permet pas à l'utilisateur de mon API d'utiliser ces fonctions.
    Juste pour préciser, ça n'est pas en contradiction avec ce que j'ai dit plus haut : le friend introduit une "faille" lorsqu'il pointe vers l'extérieur de la partie de code "sensible" (i.e. fonctionnalités à protéger, code non modifiable par d'autres a priori). Dans mon hypothèse c'était une seule classe, ici la classe friend (device) est également dans le scope.

  8. #8
    Expert éminent sénior
    Citation Envoyé par Eusebius Voir le message
    Alors le message suivant (récupéré d'un MP) est rajouté à la demande expresse de koala1, visiblement désireux de me basher en public
    Ce n'est pas mon genre , mais je vais pourtant y répondre...
    - je suis persuadé (EDIT: en d'autres termes, ça relève ici de l'intuition) qu'il est possible de mettre en place un contrôle d'accès relativement fin dans un getter/setter ;
    Primo, l'acesseur (getter) donne déjà accès à "un détail d'implémentation" qu'il est rarement judicieux d'exposer en OO, pour la simple et bonne raison que l'idée générale de ce paradigme et, justement, de baser la réflexion sur les service qu'un objet peut rendre, et non sur les données qu'il contient et qui lui permettent de rendre les services attendus.

    Si, pour une raison ou une autre, tu décide de changer la manière dont une donnée est représentée dans un objet, tu devra, non seulement modifier ton accesseur, mais également tout code qui y fait appel.

    C'est encore pire avec les mutateurs (getters) car ils impliquent que "n'importe qui" peut décider "n'importe quand" que la valeur d'une partie de l'objet ne lui convient pas, alors que cette valeur a une raison particulière d'être et qu'elle devrait, si elle est modifiée, être modifiée au fur et à mesure de l'utilisation de l'objet (comprend: lorsque l'objet rend un service donné, il peut en profiter pour modifier la valeur qui lui permet de rendre le service en question

    Secundo, la seule manière dont tu puisse envisager de gérer l'accès à une partie de ton objet (de manière tout à fait générale), et donc aux accesseurs / mutateurs, est d'en changer la visibilité.

    Le problème est qu'une partie privée ne sera accessible qu'à la classe elle-même et qu'une partie protégée ne sera accessible... qu'à la classe elle-même et aux classes qui en dérivent de manière directe ou indirecte.

    Tu peux donc envisager de mettre ton accesseur/mutateur dans l'accessibilité protégée, mais cela t'obligera à envisager de faire dériver la classe qui en a besoin de celle qui les propose.

    Or:
    • L'héritage publique est la relation la plus forte qui puisse exister et elle doit donc être utilisée avec parcimonie
    • Les héritages protégé et privé sont une relation "EST IMPLEMENE EN TERME DE", certes utiles dans certaines situations, mais qui gagnent généralement à être remplacés par des agrégations (ou des compositions) classiques
    • L'héritage, de manière générale, complexifie énormément la conception, l'évolutivité et la maintenance de ton projet.
    Je ne dis absolument pas que ce ne soit pas une possibilité à explorer dans certaines circonstances, mais je dis juste que cette possibilité est rarement applicable
    - déclarer un friend, c'est, en gros, sortir le contrôle de la classe à qui il correspond. Si tu déclares une méthode ou une classe comme friend, que le développement de ladite méthode ou classe est confié à une autre équipe qui trouve que ça serait bien pratique si ta variable privée était quand même publique,
    Tout à fait...

    Mais il faut te rappeler que l'amitié n'est ni héritée (ce n'est pas parce que Base est amie de trucmuche que Derivée (qui dérive de Base ) est amie de trucmuche) ni transitive (les amis de mes amis ne sont pas forcément mes amis ) ni réciproque (je ne suis pas forcément l'ami de ceux que je reconnais comme étant mes amis).

    Tu garde donc un contrôle complet sur "qui peut aller tripatouiller" dans les détails d'implémentation, et de manière beaucoup plus fine qu'avec les mutateurs/accesseurs
    elle a le pouvoir de la rendre publique "de fait" en implémentant un getter et un setter d'interface. Et toi qui trouvais que les getter et les setter n'étaient pas assez sécurisés, tu te retrouves avec un getter et un setter dont tu ne maîtrises même pas le code (dont tu ne connais même pas l'existence, d'ailleurs) alors que tu aurais sans doute pu faire mieux.

    C'est bien pour cela que je parle d'utiliser l'amitié en connaissance de cause et avec parcimonie...

    Par "en connaissance de cause", j'entends que tu dois avoir clairement déterminer à quoi la classe amie doit pouvoir accéder et comment, ce qui exclu, a priori, toute classes sur lequel tu n'a pas un contrôle total, et par "avec parcimonie", j'entends que tu dois, bien évidemment, veiller à ne pas déclarer une tonne d'amis simplement parce que tu ne veux pas placer de mutateur ou d'accesseur

    En résumé, c'est une considération assez théorique, mais si l'enceinte "à sécuriser" est ta classe de départ (le reste de l'application en étant a priori exclu), alors un "friend" constitue en théorie une faille dans les processus de contrôle d'accès via les mots-clés, puisqu'il permet à une classe (et donc théoriquement à toutes les autres) de les contourner.
    Tu peux effectivement en théorie, déclarer un grand nombre de classes amies d'un autre.

    Et c'est la raison pour laquelle, je le répète encore une fois, il faut envisager l'amitié avec parcimonie. Il est, en effet, tout à fait clair qu'au delà d'un nombre finalement très bas de déclarations d'amitié (maximum trois ou quatre, de manière tout à fait arbitraire), il faut envisager le fait que la présence d'accesseurs et de mutateurs soit en définitive la "moins mauvaise" solution.

    Ce n'est cependant pas une raison pour renoncer sans appel à l'amitié.

    Je t'accorde sans peine que l'amitié est à ranger au rayon des possibilités qui nous sont offertes mais qu'il faut utiliser avec extrême prudence, et uniquement lorsque c'est réellement nécessaire, au même titre que la récursivité, que le goto (et dieu sais que j'en ai horreur) ou d'autres méthodes "border line", mais si tu l'utilise a bon escient, elle s'avère d'une efficacité et d'une sécurité remarquable (pour ne pas dire inégalable).
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  9. #9
    Membre expert
    Citation Envoyé par koala01 Voir le message
    Si, pour une raison ou une autre, tu décide de changer la manière dont une donnée est représentée dans un objet, tu devra, non seulement modifier ton accesseur, mais également tout code qui y fait appel.
    Bah, pas forcément, tu peux avoir une fonction qui a un rôle d'accesseur sans qu'elle colle absolument au modèle de représentation interne. Ce n'est pas parce que tu as une fonction getX() qui te renvoie une instance de la classe Y que cette instance est effectivement une variable membre de la classe, elle peut dériver d'autre chose. J'entends "getter" dans un sens assez général, mais j'ai peut-être tort.

    Citation Envoyé par koala01 Voir le message
    C'est encore pire avec les mutateurs (getters) car ils impliquent que "n'importe qui" peut décider "n'importe quand" que la valeur d'une partie de l'objet ne lui convient pas, alors que cette valeur a une raison particulière d'être et qu'elle devrait, si elle est modifiée, être modifiée au fur et à mesure de l'utilisation de l'objet (comprend: lorsque l'objet rend un service donné, il peut en profiter pour modifier la valeur qui lui permet de rendre le service en question
    D'où ma remarque sur le possible contrôle d'accès "plus fin".

    Citation Envoyé par koala01 Voir le message
    Secundo, la seule manière dont tu puisse envisager de gérer l'accès à une partie de ton objet (de manière tout à fait générale), et donc aux accesseurs / mutateurs, est d'en changer la visibilité.
    Non, pas d'accord. Rien ne m'empêche de coder un accesseur ou un mutateur qui se fonde sur une politique de sécurité complexe pour donner ou non l'accès. On a tout C++ à notre disposition, non ? Si ça me branche, je peux très bien soumettre l'activation du mutateur à un tirage aléatoire, à une authentification RADIUS, à la consultation de l'horoscope du mois ou à n'importe quoi d'autre.

    Citation Envoyé par koala01 Voir le message
    Mais il faut te rappeler que l'amitié n'est ni héritée (ce n'est pas parce que Base est amie de trucmuche que Derivée (qui dérive de Base ) est amie de trucmuche) ni transitive (les amis de mes amis ne sont pas forcément mes amis ) ni réciproque (je ne suis pas forcément l'ami de ceux que je reconnais comme étant mes amis).

    Tu garde donc un contrôle complet sur "qui peut aller tripatouiller" dans les détails d'implémentation, et de manière beaucoup plus fine qu'avec les mutateurs/accesseurs
    Non, tu ne m'as pas compris. Je ne parle pas de la possibilité de créer une classe dérivée de la classe amie, mais de la possibilité de modifier le code de la classe amie elle-même. Mon hypothèse est très restrictive, c'est ce qui fait qu'on a l'air de ne pas être d'accord.

    Citation Envoyé par koala01 Voir le message
    C'est bien pour cela que je parle d'utiliser l'amitié en connaissance de cause et avec parcimonie...
    Tout-à-fait, cf les hypothèse de sécurité du développement.

    Citation Envoyé par koala01 Voir le message
    Par "en connaissance de cause", j'entends que tu dois avoir clairement déterminer à quoi la classe amie doit pouvoir accéder et comment
    Encore une fois, à condition d'avoir la garantie qu'elle ne peut pas être modifiée par quelqu'un d'autre.

    Citation Envoyé par koala01 Voir le message
    Ce n'est cependant pas une raison pour renoncer sans appel à l'amitié.
    Je l'ai juste dépréciée dans le cas sous-spécifié qui nous avait été soumis.

    Citation Envoyé par koala01 Voir le message
    Je t'accorde sans peine que l'amitié est à ranger au rayon des possibilités qui nous sont offertes mais qu'il faut utiliser avec extrême prudence, et uniquement lorsque c'est réellement nécessaire, au même titre que la récursivité, que le goto (et dieu sais que j'en ai horreur) ou d'autres méthodes "border line", mais si tu l'utilise a bon escient, elle s'avère d'une efficacité et d'une sécurité remarquable (pour ne pas dire inégalable).
    Attends... on n'est pas dans le même débat là, et probablement dans le troll, mais... tu mets la récursivité, un des modèles à la base de la notion même de programmation, au même niveau que le goto, qui sert principalement à empêcher toute analyse sémantique ??

  10. #10
    Expert éminent sénior
    Citation Envoyé par Eusebius Voir le message
    Bah, pas forcément, tu peux avoir une fonction qui a un rôle d'accesseur sans qu'elle colle absolument au modèle de représentation interne. Ce n'est pas parce que tu as une fonction getX() qui te renvoie une instance de la classe Y que cette instance est effectivement une variable membre de la classe, elle peut dériver d'autre chose. J'entends "getter" dans un sens assez général, mais j'ai peut-être tort.
    Imagine un simple accesseur renvoyant un entier (getx() const) renvoyant la coordonnée sur l'axe des X de n'importe quoi.

    Il est, sans doute, suivi de près par un autre accesseur renvoyant un autre entier (getY() const) renvoyant la coordonnée sur l'axe Y de ce même n'importe quoi.

    Tu te rend compte que, finalement, X et Y vont tellement bien ensemble qu'il est intéressant d'en faire une structure (ou une classe).

    Soit tu dois modifier tes deux accesseurs pour que l'un renvoye Coordonnee.x (ou Coordonnee[0], selon le cas) et l'autre Coordonnee.y (respectivement Coordonnee[1]), soit tu vire carréement tes deux accesseurs, et tu en crée un autre "getCoordonnee() const"...

    Et là, tout le code qui utilise getX et getY est bon pour la poubelle
    D'où ma remarque sur le possible contrôle d'accès "plus fin".
    Le controle le plus fin que tu puisse donner à un mutateur, c'est qu'il n'existe pas en tant que telle, mais qu'il soit intégré dans les services rendus par ta classe

    Mais, à partir du moment où il est accessible publiquement, tu n'a plus le choix: il est accessible pour ... tout le monde et n'importe quand

    Non, pas d'accord. Rien ne m'empêche de coder un accesseur ou un mutateur qui se fonde sur une politique de sécurité complexe pour donner ou non l'accès. On a tout C++ à notre disposition, non ? Si ça me branche, je peux très bien soumettre l'activation du mutateur à un tirage aléatoire, à une authentification RADIUS, à la consultation de l'horoscope du mois ou à n'importe quoi d'autre.
    Bien sur que tu peux mettre en place un certain nombre de sécurités sur ton mutateur...

    Mais une bonne partie de ces sécurités échoira... à celui qui l'appelle, alors qu'une autre (celle qui ne sait absolument pas qui essaye d'y avoir recours) sera finalement fort limité dans les vérifications qu'elle peut entreprendre

    Et comme, à la base, tout le monde peut faire appel au mutateur (à moins d'en changer la visibilité)... quoi tu compte faire confiance à l'appelant pour avoir vérifier qu'il avait bien le droit d'appeler le mutateur

    Non, tu ne m'as pas compris. Je ne parle pas de la possibilité de créer une classe dérivée de la classe amie, mais de la possibilité de modifier le code de la classe amie elle-même. Mon hypothèse est très restrictive, c'est ce qui fait qu'on a l'air de ne pas être d'accord.
    C'est la raison pour laquelle je dis qu'il faut avoir le contrôle total sur la classe que l'on déclare amie.

    La classe amie ne pouvant être modifiée que par toi ou ton équipe, et devant surtout être suffisamment documentée pour que, en cas de modification, on puisse veiller à ce qu'elle n'accède qu'à ce à quoi elle a droit
    Encore une fois, à condition d'avoir la garantie qu'elle ne peut pas être modifiée par quelqu'un d'autre.
    De prime abord, lorsque tu fournit une classe, soit tu donne le code (en-tête + implémentation) à quelqu'un en qui tu as suffisamment confiance pour lui laisser la modifier, soit tu ne donne que le stricte minimum (en-tête) à celui qui l'utilisera
    Attends... on n'est pas dans le même débat là, et probablement dans le troll, mais... tu mets la récursivité, un des modèles à la base de la notion même de programmation, au même niveau que le goto, qui sert principalement à empêcher toute analyse sémantique ??
    Je met la récurisvité, le goto, les variables statiques et l'amitié dans le même sac de choses qu'il faut utiliser intelligemment, jamais à la légère, et lorsqu'il apparait clairement que c'est la moins mauvaise solution, en effet, et ce n'est pas un troll:
    • Bien que je sois grand défenseur de récursivité, j'ai les cheveux qui se dressent sur ma tête à l'idée de rencontrer une fonction récursive pour calculer une exponentielle ou une factorielle.
    • A l'inverse, bien que j'ai une sainte horreur du goto, j'admets (et ca me fait mal au bide ) qu'il puisse, dans certaines circonstances, s'avérer utile.


    Ce n'est bien sur que mon avis personnel, et je te laisse libre d'avoir le tien sur le sujet, mais je me suis déjà exprimé dans les discussions qui y étaient relatives

    Je n'ai plus les adresses en tête, mais une petite recherche devrait te permettre de retrouver les discussions en question
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  11. #11
    Membre émérite
    Je rajouterai à ce qui a déjà été dit par Koala que déterminer que certaines fonctionnalités ne sont accessibles qu'à certains utilisateurs me parait un besoin crucial. D'ailleurs, beaucoup de langages fournissent un mécanisme pour ce faire :

    C# fournit internal, qui a comme granularité la propriété, mais restreint l'appelant seulement à tout le package (c'est beaucoup). Java fait la même chose me semble-t-il.

    C++ fournit friend, qui est très fin sur l'appelant (classe ou fonction libre), mais pas sur la propriété (toute la classe devient accessible)

    Eiffel a la double granularité, mais il faut être prudent car elle est héritée (si ma méthode est disponible pour A, elle l'est pour toute classe dérivée de A.


    Je ne saurais pas dire laquelle de ces approches est la meilleure, bien que ma préférence aille vers le modèle « Eiffel ». Mais pour qu'on ait à chaque fois intégré un mécanisme tel dans le langage, c'est que ça répond à un réel besoin.

    Enfin,

    Rien ne m'empêche de coder un accesseur ou un mutateur qui se fonde sur une politique de sécurité complexe pour donner ou non l'accès. On a tout C++ à notre disposition, non ? Si ça me branche, je peux très bien soumettre l'activation du mutateur à un tirage aléatoire, à une authentification RADIUS, à la consultation de l'horoscope du mois ou à n'importe quoi d'autre.
    Tu confonds deux choses :
    - la vérification statique, faite par le compilateur, destinée à aider le programmeur à ne pas faire n'importe quoi
    - la vérification dynamique, pour améliorer la sécurité du programme

    private, public, friend et consorts sont, en C++, des informations purement statiques. Destinées à simplifier la vie du programmeur et l'aider à ne pas faire de bêtises. Certainement pas des fonctionnalités de sécurité.

  12. #12
    Membre éclairé
    Je suis vraiment content de ce débat, car c'est un des points sur lequel j'hésite le plus quand je conçois une classe..

    J'essaye de ne jamais utilisé public, et friend le moins possible.

    Je l'utilise malgré tout pour créer mes singletons (classe template Singleton). Les classes que je déclare singleton sont amies, en ce qui concerne les méthode getInstance et kill avec le singleton.

    Est-ce un cas valide d'utilisation de friend?

  13. #13
    Membre chevronné
    Citation Envoyé par seeme Voir le message
    Je suis vraiment content de ce débat, car c'est un des points sur lequel j'hésite le plus quand je conçois une classe..

    J'essaye de ne jamais utilisé public, et friend le moins possible.

    Je l'utilise malgré tout pour créer mes singletons (classe template Singleton). Les classes que je déclare singleton sont amies, en ce qui concerne les méthode getInstance et kill avec le singleton.

    Est-ce un cas valide d'utilisation de friend?
    si on considére le singleton comme valide ... ce qui est tout relatif.

    J'abonde dans le sens d'Emmanuel : avec parcimonie, friend résout des problèmes fins sans tout mettre par terre.

  14. #14
    Expert confirmé
    Citation Envoyé par seeme Voir le message
    Je suis vraiment content de ce débat, car c'est un des points sur lequel j'hésite le plus quand je conçois une classe..

    J'essaye de ne jamais utilisé public, et friend le moins possible.

    Je l'utilise malgré tout pour créer mes singletons (classe template Singleton). Les classes que je déclare singleton sont amies, en ce qui concerne les méthode getInstance et kill avec le singleton.

    Est-ce un cas valide d'utilisation de friend?
    Hélas, si je cherche à répondre à la question, je vais commencer par te dire que les singletons, c'est pas bon. Donc utiliser friend dans un singleton, j'ai du mal à en voir l'intérêt
    Cette signature n'a pas pu être affichée car elle comporte des erreurs.

  15. #15
    Expert éminent sénior
    Citation Envoyé par Emmanuel Deloget Voir le message
    Hélas, si je cherche à répondre à la question, je vais commencer par te dire que les singletons, c'est pas bon. Donc utiliser friend dans un singleton, j'ai du mal à en voir l'intérêt
    Il est vrai que, de manière générale, le meilleur moyen de s'assurer qu'il n'y aura qu'une instance d'un objet est encore... de veiller à n'en créer qu'une

    Ceci dit, une implémentation que l'on retrouve souvent pour les singleton est proche de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    template <typename T>
    class Singleton
    {
        public:
            static T & instance()
            {
                static T inst;
                return inst;
            }
        private:
            Singleton(){}
            ~Singleton(){}
    };
    Ce qui justifie la déclaration d'amitié à cause du CRTP:
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    class MonSingleton : public MonSigleton
    {
        friend class Singleton<MonSingleton>; // seul moyen pour que Singleton 
                                              // puisse accéder au constructeur
        private:
            MonSingleton(){}
            ~MonSingleton(){}
    };

    Mais ce n'est clairement pas la raison la plus fréquente de recourir à l'amitié

    Je voudrais aussi revenir sur le terme parcimonie...

    Cela ne me dérangerait absolument pas, dans un projet de trouver cinquante
    classe déclarant chacune une amitié avec une classe différente et d'avoir, pour l'exemple,
    • class1 amie de friend1 (et uniquement de friend1)
    • class2 amie de friend2 (et uniquement de friend2)
    • class3 amie de friend3 (et uniquement de friend3)
    • ...
    • classN amie de friendN (et uniquement de friendN)


    Par contre, si je trouve une seule classe qui en déclare plus de trois ou quatre amitiés(de manière, je l'avoue, tout à fait arbitraire), je commencerai réellement à me poser sérieusement la question de savoir s'il n'y a pas un problème de conception

    Il faut être bien clair: la parcimonie est de rigueur, mais au niveau du nombre de classes amies que peut compter une classe "unique", et non au niveau du nombre total de fois que l'amitié est utilisée dans le projet
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  16. #16
    Membre éclairé
    Avec un peu de pragmatisme il me semble évident qu'il faille une visibilité intermédiaire entre public et protected, pour des raisons de simplicité.
    La plupart des langages possèdent cette fonctionnalité. Ceux basés sur un système de modules/packages, comme Java, C# ou D proposent une visibilité à toutes les classes du même package. Friend permet de limiter la visibilité au plus juste et de contrôler qui est autorisé à voir quoi sur un principe de liste blanche, c'est à mon avis plus sûr.
    Après j'adhère tout à fait au principe qu'il faille l'utiliser avec parcimonie.
    "By and large I'm trying to minimize mentions of D in C++ contexts because it's as unfair as bringing a machine gun to a knife fight." - Andrei Alexandrescu

  17. #17
    Membre expert
    Personellement, j'ai pris l'habitude de prendre friend comme une forme d'extension de type, permettant d'englober plusieurs types et fonctions dans la même encapsulation (en admettant que tout soit correctement encapsulé).

    Cela dit, je trouve friend imparfait. Ce qui serait pratique, autant pour communiquer avec les autres développeurs que pour fixer des protocoles de communication entre les classes - serait que friend puisse s'appliquer à une fonction membre spécique d'une classe, au lieu d'une classe entière. Cela donnerait un nom à l'amitié en question.

    Je crois que j'en ai parlé dans une ancienne discussion, mais je n'ai pas encore formalisé l'idée. Peut être que c'est suffisamment valide pour une proposition au standard? °__°/

  18. #18
    Expert confirmé
    Citation Envoyé par Klaim Voir le message

    Cela dit, je trouve friend imparfait. Ce qui serait pratique, autant pour communiquer avec les autres développeurs que pour fixer des protocoles de communication entre les classes - serait que friend puisse s'appliquer à une fonction membre spécique d'une classe, au lieu d'une classe entière. Cela donnerait un nom à l'amitié en question.
    Ah, il faudrait que tu teste ça :

    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
     
    class B;
     
    class A
    {
    public:
      void f1(B& b)
      {
        b.priv();
      }
      void f2(B& b)
      {
        b.priv();
      }
    };
     
    class B:
    {
    private:
      void priv() { };
     
      friend void A::f1(B&);
    };


    C'est surprenant ce qu'on peut faire avec un standard, hein ? (11.5§5 dans N3126). Je ne sais plus, par contre, si le support date de cette nouvelles version du standard ou s'il existait avant. Si quelqu'un a la version du draft pre-2003, ou le standard C++ de 1998 ou 2003, il pourra confirmer ça (chapitre [class.friend])

    EDIT: j'ai regardé aussi loin que je pouvais (draft de 2004) et c'était visiblement déjà possible à cette époque. A noter que les constructeurs et destructeurs peuvent être friend d'une classe.

    EDIT: et j'ai finalement retrouvé le final draft de 1998. 11.4§4 dit la même chose. Donc c'est un comportement qui existe déjà, même s'il est vrai qu'il est peu usité. En tout cas, merci de me l'avoir rappelé. C'est vrai que ça ouvre un certain nombre de possibilités.
    Cette signature n'a pas pu être affichée car elle comporte des erreurs.

  19. #19
    Membre expert
    O___O

    Ben je me disais aussi qu'en pratiquant je suis souvent tombé sur cette reflexion souvent donc je pensais que quelqu'un avait du y penser avant moi...et en fait j'avais raison;

    Mais ça ne marche pas dans le cas de types externe au type courant? J'ai rien dit j'avais mal lu le code.

    Excellent!

  20. #20
    Membre expert
    Ah mais non je me souviens de ce que précisément j'avais discuté ici (je crois que c'était ici du moins) : le fait de ne pas pouvoir dire que le friend ne s'applique pas à toute la classe courante mais à une seule fonction. En gros "cabler" deux fonctions ensemble.

    Je ne retrouve plus la discussion par contre...

###raw>template_hook.ano_emploi###