Developpez.com

Plus de 2 000 forums
et jusqu'à 5 000 nouveaux messages par jour

Pour ou contre l'amitié entre classes

Le , par Eusebius, Membre expert
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.


Vous avez aimé cette actualité ? Alors partagez-la avec vos amis en cliquant sur les boutons ci-dessous :


 Poster une réponse

Avatar de koala01 koala01 - Expert éminent sénior http://www.developpez.com
le 20/11/2010 à 12:07
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
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
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
Avatar de Niark13 Niark13 - Membre éclairé http://www.developpez.com
le 25/11/2010 à 12:14
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.
Avatar de Klaim Klaim - Membre expert http://www.developpez.com
le 25/11/2010 à 12:58
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? °__°/
Avatar de Emmanuel Deloget Emmanuel Deloget - Expert confirmé http://www.developpez.com
le 25/11/2010 à 13:42
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
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.
Avatar de Klaim Klaim - Membre expert http://www.developpez.com
le 25/11/2010 à 15:22
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!
Avatar de Klaim Klaim - Membre expert http://www.developpez.com
le 25/11/2010 à 15:28
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...
Avatar de - http://www.developpez.com
le 25/11/2010 à 15:29
ce que j'aurais aimé voir en C++, c'est un contrôle plus fin comme:

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
class A 
{ 
public: 
  A(); 
  ~A(); 
private: //privé privé! 
  int m_a; 
friend class B: //privé, sauf pour B 
  int m_b; 
};
histoire que B n'aie pas soudainement accès a toute la classe. Cela permet de définir trés précisément qui a accès a quoi

un exemple de friend assez utile:
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Mutex 
{ 
    friend class ScopedMutexLock; 
private: 
    void*           m_data; 
public: 
    Mutex(); 
    ~Mutex(); 
private: 
    void release(); 
    void wait(); 
}; 
 
class ScopedMutexLock 
{ 
private: 
    Mutex&  m_mutex; 
public: 
    inline ScopedMutexLock(Mutex& m) : m_mutex(m)   { m_mutex.wait(); } 
    inline ~ScopedMutexLock()                       { m_mutex.release(); } 
private: 
    ScopedMutexLock& operator=(const ScopedMutexLock& other); 
    ScopedMutexLock(const ScopedMutexLock& other); 
};
ainsi on ne peut pas vérouiller un mutex partout comme un cochon et oublier d'appeler release; on ne peut accéder a ces fonctions qu'a travers un objet Lock
Avatar de koala01 koala01 - Expert éminent sénior http://www.developpez.com
le 25/11/2010 à 15:53
Malheureusement, si tu déclare quelque chose comme étant ami de ta classe, ce que tu déclare ami a acces à tout...

Mais... Il faut se rappeler que l'amitié n'est ni héritée, ni transitive ni réciproque

Au risque de complexifier ton modèle, tu peux donc parfaitement envisager de créer une classe parent supplémentaire, qui ne contient que ce à quoi tu souhaite que la classe amie aie réellement accès:
Code : Sélectionner tout
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
class PrivateClass 
{ 
friend class FriendClass; 
public: 
protected: 
    int i; // in protected accessibility so that derived class have access 
           // to i without to need any getter / setter 
    PrivateClass():i(153){} 
}; 
class Derived : public PrivateClass 
{ 
public: 
    Derived():j(2){} 
private: 
    int j; 
}; 
class FriendClass 
{ 
public: 
    void changeValues(Derived & d) 
    { 
        std::cout<<"before change, i="<<d.i<<std::endl; 
        d.i=15; // OK due to friendship with PrivateClass 
     // d.j=63;  // KO: no friendship with Derived: j is inaccessible 
        std::cout<<"after changes, i="<<d.i<<std::endl; 
    } 
};
[EDIT]PS: nous arrivons, peut être, dans une situation dans laquelle nous pourrions justifier le non respect de LSP dont il est question ici
Avatar de Emmanuel Deloget Emmanuel Deloget - Expert confirmé http://www.developpez.com
le 26/11/2010 à 11:04
Citation Envoyé par screetch  Voir le message
ce que j'aurais aimé voir en C++, c'est un contrôle plus fin comme:

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
class A 
{ 
public: 
  A(); 
  ~A(); 
private: //privé privé! 
  int m_a; 
friend class B: //privé, sauf pour B 
  int m_b; 
};
histoire que B n'aie pas soudainement accès a toute la classe. Cela permet de définir trés précisément qui a accès a quoi

Je plussoie, ça serait quelque chose de très sympathique. On peut toutefois contourner en partie le problème en composant les objets différemment. Mais ce n'est qu'un contournement qui, bien que tout à fait légitime, n'en est pas moins limité et peu agréable.

En fait, je pense qu'il aurait fallu au niveau de la syntaxe quelque chose de plus général, peut-être ressemblant à la capture list des lambda.

Citation Envoyé par screetch  Voir le message
ainsi on ne peut pas vérouiller un mutex partout comme un cochon et oublier d'appeler release; on ne peut accéder a ces fonctions qu'a travers un objet Lock

Voui et non. J'ai du mal à formaliser mon désaccord, mais je pense que c 'est plus contre-productif qu'autre chose. Au niveau de l'interface, comment implémentes-tu le try-lock? Et dans ce cas, le release (si le lock a réussi) ?
Avatar de - http://www.developpez.com
le 26/11/2010 à 12:56
déjà j'évite le trylock donc j'avoue que le probleme c'est jamais posé pour moi
le code multithread est deja suffisemment complexe pour ne pas rajouter des cas particuliers par dessus

si c'est utile, un truc comme:
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ScopedMutexTryLock 
{ 
private: 
    Mutex&  m_mutex; 
    bool m_result; 
public: 
    inline ScopedMutexTryLock(Mutex& m) : m_mutex(m), m_result(m.trylock())   { } 
    inline ~ScopedMutexTryLock()                       { if(m_result) m_mutex.release(); } 
    operator bool() const { return m_result; } 
    bool operator !() const { return !m_result; } 
}; 
 
int foo() 
{ 
   ScopedMutexTryLock lock(m_mutex); 
   if(lock) 
   { 
   } 
}
c'est forcément gênant puisque le if est "optionnel" et il est possible de le confondre très facilement avec le ScopedMutexLock. Mais comme dit plus haut, je ne l'ai pas ajouté donc le problème se pose pas pour moi.

Après pour la contre-productivité, ca se discute. Moi je dis que le couple lock/release a un coût caché qui est le temps de debugging que l'objet lock n'a pas. et j'aime bien que l'on prenne en compte le temps perdu a réparer un truc. Mais je comprends ce que tu veux dire, c'est un peu moins flexible et il se peut qu'il faille se contortionner un peu pour l'utiliser.
Avatar de Emmanuel Deloget Emmanuel Deloget - Expert confirmé http://www.developpez.com
le 26/11/2010 à 13:48
Citation Envoyé par screetch  Voir le message
déjà j'évite le trylock donc j'avoue que le probleme c'est jamais posé pour moi
le code multithread est deja suffisemment complexe pour ne pas rajouter des cas particuliers par dessus

si c'est utile, un truc comme:
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ScopedMutexTryLock 
{ 
private: 
    Mutex&  m_mutex; 
    bool m_result; 
public: 
    inline ScopedMutexTryLock(Mutex& m) : m_mutex(m), m_result(m.trylock())   { } 
    inline ~ScopedMutexTryLock()                       { if(m_result) m_mutex.release(); } 
    operator bool() const { return m_result; } 
    bool operator !() const { return !m_result; } 
}; 
 
int foo() 
{ 
   ScopedMutexTryLock lock(m_mutex); 
   if(lock) 
   { 
   } 
}
c'est forcément gênant puisque le if est "optionnel" et il est possible de le confondre très facilement avec le ScopedMutexLock. Mais comme dit plus haut, je ne l'ai pas ajouté donc le problème se pose pas pour moi.

Après pour la contre-productivité, ca se discute. Moi je dis que le couple lock/release a un coût caché qui est le temps de debugging que l'objet lock n'a pas. et j'aime bien que l'on prenne en compte le temps perdu a réparer un truc. Mais je comprends ce que tu veux dire, c'est un peu moins flexible et il se peut qu'il faille se contortionner un peu pour l'utiliser.

Solution intéressante.

Et remarque intéressante aussi. C'est vrai que le lock/unlock explicite peut provoquer bien des problèmes. Il faudrait que je réfléchisse plus profondément à la question, mais j'entre-aperçois déjà une lumière de sagesse dans ce que tu dis.
Offres d'emploi IT
Analyste développeur confirmé/senior C++ h/f
Sigmalis - Suisse - Lausanne
Ingénieur en développement C++
small IZ beautiful - Ile de France - Orsay (91400)
Data scientist h/f
AXA - Ile de France - Paris - Avenue Matignon

Voir plus d'offres Voir la carte des offres IT
Contacter le responsable de la rubrique C++