[c++] Quand devriez-vous utiliser 'friend' en C ++?



Answers

Au travail, nous utilisons des amis pour tester le code , largement. Cela signifie que nous pouvons fournir une encapsulation et une dissimulation d'informations appropriées pour le code d'application principal. Mais nous pouvons aussi avoir un code de test séparé qui utilise des amis pour inspecter l'état interne et les données pour les tests.

Autant dire que je n'utiliserais pas le mot clé ami comme composant essentiel de votre design.

Question

J'ai lu la FAQ C ++ et j'étais curieux de la déclaration d' friend . Personnellement, je ne l'ai jamais utilisé, mais je suis intéressé par l'exploration de la langue.

Quel est un bon exemple d'utilisation d'un friend ?

En lisant la FAQ un peu plus longtemps, j'aime l'idée que l'opérateur << >> surcharge et ajoute en tant qu'ami de ces classes. Cependant, je ne suis pas sûr de savoir comment cela ne casse pas l'encapsulation. Quand ces exceptions peuvent-elles rester dans les limites de la POO?




Probably I missed something from the answers above but another important concept in encapsulation is hiding of implementation. Reducing access to private data members (the implementation details of a class) allows much easier modification of the code later. If a friend directly accesses the private data, any changes to the implementation data fields (private data), break the code accessing that data. Using access methods mostly eliminates this. Fairly important I would think.




When implementing tree algorithms for class, the framework code the prof gave us had the tree class as a friend of the node class.

It doesn't really do any good, other than let you access a member variable without using a setting function.




Le créateur de C ++ dit qu'il ne respecte aucun principe d'encapsulation, et je vais le citer:

Est-ce que "ami" viole l'encapsulation? Non. "Friend" est un mécanisme explicite d'octroi d'accès, tout comme l'adhésion. Vous ne pouvez pas (dans un programme conforme standard) vous accorder l'accès à une classe sans modifier sa source.

Est plus que clair ...




Vous contrôlez les droits d'accès pour les membres et les fonctions en utilisant Privé / Protégé / Droit public? donc en supposant que l'idée de chacun de ces trois niveaux est claire, alors il devrait être clair que nous manquons quelque chose ...

La déclaration d'un membre / fonction comme protégé par exemple est assez générique. Vous dites que cette fonction est hors de portée pour tout le monde (sauf pour un enfant hérité bien sûr). Mais qu'en est-il des exceptions? chaque système de sécurité vous permet d'avoir un certain type de «liste blanche» à droite?

Donc, un ami vous permet d'avoir la possibilité d'isoler des objets solides, mais permet de créer une "échappatoire" pour les choses que vous jugez justifiées.

Je suppose que les gens disent que ce n'est pas nécessaire parce qu'il y a toujours un design qui va s'en passer. Je pense que c'est similaire à la discussion des variables globales: Vous ne devriez jamais les utiliser, Il y a toujours un moyen de s'en passer ... mais en réalité, vous voyez des cas où cela finit par être (presque) le plus élégant. .. Je pense que c'est le même cas avec des amis.

Cela ne sert à rien, sauf que vous pouvez accéder à une variable membre sans utiliser de fonction de réglage

bien ce n'est pas exactement la façon de le regarder. L'idée est de contrôler que l'OMS peut avoir accès à ce que, avoir ou non une fonction de réglage a peu à voir avec elle.




Une autre utilisation: friend (+ héritage virtuel) peut être utilisée pour éviter de dériver d'une classe (alias: "make a class underivable") => 1 , 2

De 2 :

 class Fred;

 class FredBase {
 private:
   friend class Fred;
   FredBase() { }
 };

 class Fred : private virtual FredBase {
 public:
   ...
 }; 



@roo : L'encapsulation n'est pas interrompue ici car la classe elle-même dicte qui peut accéder à ses membres privés. L'encapsulation ne serait brisée que si cela pouvait être causé en dehors de la classe, par exemple si votre operator << proclame "que je suis un ami de classe foo ."

friend remplace l'utilisation du public , pas l'usage du private !

En fait, la FAQ C ++ répond déjà à cela .




As the reference for friend declaration says:

The friend declaration appears in a class body and grants a function or another class access to private and protected members of the class where the friend declaration appears.

So just as a reminder, there are technical errors in some of the answers which say that friend can only visit protected members.




Vous devez faire très attention quand et où vous utilisez le mot-clé friend , et, comme vous, je l'ai rarement utilisé. Voici quelques notes sur l'utilisation d'un friend et les alternatives.

Disons que vous voulez comparer deux objets pour voir s'ils sont égaux. Vous pouvez soit:

  • Utilisez les méthodes d'accesseur pour faire la comparaison (vérifiez chaque ivar et déterminez l'égalité).
  • Ou, vous pouvez accéder directement à tous les membres en les rendant publics.

Le problème avec la première option, c'est que cela pourrait être beaucoup d'accesseurs, ce qui est (légèrement) plus lent que l'accès direct à la variable, plus difficile à lire et lourd. Le problème avec la deuxième approche est que vous rompez complètement l'encapsulation.

Ce qui serait bien, c'est de pouvoir définir une fonction externe qui pourrait encore avoir accès aux membres privés d'une classe. Nous pouvons le faire avec le mot-clé friend :

class Beer {
public:
    friend bool equal(Beer a, Beer b);
private:
    // ...
};

La méthode equal(Beer, Beer) maintenant un accès direct aux membres privés de a et b (qui peuvent être char *brand , float percentAlcohol , etc. Ceci est un exemple plutôt artificiel, vous préféreriez appliquer un friend à un overloaded == operator , mais nous y reviendrons.

Quelques choses à noter:

  • Un friend n'est PAS une fonction membre de la classe
  • C'est une fonction ordinaire avec un accès spécial aux membres privés de la classe
  • Ne remplacez pas tous les accesseurs et mutateurs par des amis (vous pouvez aussi rendre tout public !)
  • L'amitié n'est pas réciproque
  • L'amitié n'est pas transitive
  • L'amitié n'est pas héritée
  • Ou, comme l' explique la FAQ C ++ : "Le fait que je vous accorde un accès ami n'autorise pas automatiquement vos enfants à accéder à moi, n'autorise pas automatiquement vos amis à accéder à moi et ne m'accorde pas automatiquement l'accès "

Je n'utilise vraiment que des friends quand il est beaucoup plus difficile de le faire dans l'autre sens. Autre exemple, de nombreuses fonctions mathématiques vectorielles sont souvent créées en tant friends grâce à l'interopérabilité de Mat2x2 , Mat3x3 , Mat4x4 , Vec2 , Vec3 , Vec4 , etc. Et c'est tellement plus facile d'être ami, plutôt que d'avoir à utiliser des accesseurs partout. Comme indiqué, l' friend est souvent utile lorsqu'il est appliqué au << (très pratique pour le débogage), >> et peut-être l'opérateur == , mais peut aussi être utilisé pour quelque chose comme ceci:

class Birds {
public:
    friend Birds operator +(Birds, Birds);
private:
    int numberInFlock;
};


Birds operator +(Birds b1, Birds b2) {
    Birds temp;
    temp.numberInFlock = b1.numberInFlock + b2.numberInFlock;
    return temp;
}

Comme je le dis, je n'utilise pas très souvent d' friend , mais de temps en temps, c'est exactement ce dont vous avez besoin. J'espère que cela t'aides!




Pour faire TDD plusieurs fois j'ai utilisé le mot-clé 'friend' en C ++.
Un ami peut-il tout savoir de moi?

Non, c'est seulement une amitié à sens unique: `(




Friends are also useful for callbacks. You could implement callbacks as static methods

class MyFoo
{
private:
    static void callback(void * data, void * clientData);
    void localCallback();
    ...
};

where callback calls localCallback internally, and the clientData has your instance in it. In my opinion,

or...

class MyFoo
{
    friend void callback(void * data, void * callData);
    void localCallback();
}

What this allows is for the friend to be a defined purely in the cpp as a c-style function, and not clutter up the class.

Similarly, a pattern I've seen very often is to put all the really private members of a class into another class, which is declared in the header, defined in the cpp, and friended. This allows the coder to hide a lot of the complexity and internal working of the class from the user of the header.

In the header:

class MyFooPrivate;
class MyFoo
{
    friend class MyFooPrivate;
public:
    MyFoo();
    // Public stuff
private:
    MyFooPrivate _private;
    // Other private members as needed
};

In the cpp,

class MyFooPrivate
{
public:
   MyFoo *owner;
   // Your complexity here
};

MyFoo::MyFoo()
{
    this->_private->owner = this;
}

It becomes easier to hide things that the downstream needn't see this way.




Ami est pratique lorsque vous construisez un conteneur et que vous voulez implémenter un itérateur pour cette classe.




En ce qui concerne l'opérateur << et l'opérateur >>, il n'y a aucune raison de rendre ces opérateurs amis. Il est vrai qu'ils ne devraient pas être des fonctions membres, mais ils n'ont pas besoin d'être amis non plus.

La meilleure chose à faire est de créer des fonctions publiques d'impression (ostream &) et de lecture (istream &). Ensuite, écrivez l'opérateur << et l'opérateur >> en termes de ces fonctions. Cela donne l'avantage supplémentaire de vous permettre de rendre ces fonctions virtuelles, ce qui fournit une sérialisation virtuelle.




edit: En lisant la faq un peu plus longtemps, j'aime l'idée de surcharger l'opérateur << >> et l'ajouter en tant qu'ami de ces classes, mais je ne suis pas sûr que cela ne brise pas l'encapsulation

Comment cela romprait l'encapsulation?

Vous cassez l'encapsulation lorsque vous autorisez un accès illimité à un membre de données. Considérez les classes suivantes:

class c1 {
public:
  int x;
};

class c2 {
public:
  int foo();
private:
  int x;
};

class c3 {
  friend int foo();
private:
  int x;
};

c1 n'est évidemment pas encapsulé. Tout le monde peut lire et modifier x dedans. Nous n'avons aucun moyen d'imposer un contrôle d'accès.

c2 est évidemment encapsulé. Il n'y a pas d'accès public à x . Tout ce que vous pouvez faire est d'appeler la fonction foo , qui effectue une opération significative sur la classe .

c3 ? Est-ce moins encapsulé? Permet-il un accès illimité à x ? Permet-il l'accès aux fonctions inconnues?

Non. Elle permet précisément d'accéder aux membres privés de la classe. Tout comme c2 fait. Et tout comme c2 , la seule fonction qui a accès n'est pas "une fonction aléatoire, inconnue", mais "la fonction listée dans la définition de la classe". Tout comme c2 , nous pouvons voir, juste en regardant les définitions de classe, une liste complète de qui a accès.

Alors, comment exactement est-ce moins encapsulé? La même quantité de code a accès aux membres privés de la classe. Et tous ceux qui ont accès sont listés dans la définition de classe.

friend ne casse pas l'encapsulation. Cela fait que certains programmeurs Java se sentent mal à l'aise, parce que quand ils disent "OOP", ils veulent dire "Java". Quand ils disent "encapsulation", ils ne signifient pas "les membres privés doivent être protégés contre les accès arbitraires", mais "une classe Java où les seules fonctions capables d'accéder aux membres privés sont des membres de classe", même si cela est complètement absurde. plusieurs raisons .

Premièrement, comme déjà montré, c'est trop restrictif. Il n'y a aucune raison pour que les méthodes d'amis ne puissent pas faire la même chose.

Deuxièmement, ce n'est pas assez restrictif. Considérons une quatrième classe:

class c4 {
public:
  int getx();
  void setx(int x);
private:
  int x;
};

Ceci, selon la mentalité Java ci-dessus, est parfaitement encapsulé. Et pourtant, il permet absolument à quiconque de lire et de modifier x . Comment cela a-t-il même un sens? (Indice: Ce n'est pas le cas)

Bottom line: L'encapsulation consiste à être en mesure de contrôler quelles fonctions peuvent accéder aux membres privés. Il ne s'agit pas précisément de la localisation des définitions de ces fonctions.




Les fonctions et les classes Friend fournissent un accès direct aux membres privés et protégés de la classe pour éviter de casser l'encapsulation dans le cas général. La plupart des utilisations sont avec ostream: nous aimerions pouvoir taper:

Point p;
cout << p;

Cependant, cela peut nécessiter l'accès aux données privées de Point, donc nous définissons l'opérateur surchargé

friend ostream& operator<<(ostream& output, const Point& p);

Cependant, il y a des implications évidentes d'encapsulation. Premièrement, la classe ou la fonction d'un ami a maintenant accès à TOUS les membres de la classe, même ceux qui ne correspondent pas à ses besoins. Deuxièmement, les implémentations de la classe et de l'ami sont maintenant enchevêtrées au point où un changement interne dans la classe peut briser l'ami.

Si vous voyez l'ami comme une extension de la classe, alors ce n'est pas un problème, logiquement parlant. Mais, dans ce cas, pourquoi était-il nécessaire de parler de l'ami en premier lieu.

Pour atteindre la même chose que les «amis» prétendent atteindre, mais sans casser l'encapsulation, on peut faire ceci:

class A
{
public:
    void need_your_data(B & myBuddy)
    {
        myBuddy.take_this_name(name_);
    }
private:
    string name_;
};

class B
{
public:
    void print_buddy_name(A & myBuddy)
    {
        myBuddy.need_your_data(*this);
    }
    void take_this_name(const string & name)
    {
        cout << name;
    }
}; 

L'encapsulation n'est pas cassée, la classe B n'a pas accès à l'implémentation interne dans A, pourtant le résultat est le même que si nous avions déclaré B un ami de A. Le compilateur optimisera les appels de fonction, donc cela se traduira par la même instructions comme accès direct.

Je pense qu'utiliser «ami» est simplement un raccourci avec un avantage discutable, mais un coût certain.




Links