Quand devriez-vous utiliser «ami» en C ++?


354

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

Quel est un bon exemple d'utilisation friend?


En lisant la FAQ un peu plus longtemps, j'aime l'idée de la << >>surcharge de l' opérateur et de l'ajouter comme ami de ces classes. Cependant, je ne sais pas comment cela ne rompt pas l'encapsulation. Quand ces exceptions peuvent-elles rester dans la rigueur de la POO?


5
Bien que je sois d'accord avec la réponse selon laquelle une classe d'amis n'est pas nécessairement une mauvaise chose, j'ai tendance à la traiter comme un petit code. Cela indique souvent, mais pas toujours, que la hiérarchie des classes doit être reconsidérée.
Mawg dit réintégrer Monica le

1
Vous utiliseriez une classe d'amis où il y a déjà un couplage étroit. C'est pour ça qu'il est fait. Par exemple, une table de base de données et ses index sont étroitement couplés. Lorsqu'une table change, tous ses index doivent être mis à jour. Ainsi, la classe DBIndex déclarerait DBTable comme ami afin que DBTable puisse accéder directement aux internes de l'index. Mais il n'y aurait pas d'interface publique avec DBIndex; il n'est même pas logique de lire un index.
shawnhcorey

Les «puristes» de la POO ayant peu d'expérience pratique soutiennent qu'un ami viole les principes de la POO, car une classe devrait être le seul responsable de son état privé. C'est très bien, jusqu'à ce que vous rencontriez une situation commune où deux classes doivent maintenir un état privé partagé.
kaalus

Réponses:


335

Tout d'abord (l'OMI) n'écoute pas les gens qui disent friend n'est pas utile. C'est utile. Dans de nombreuses situations, vous aurez des objets avec des données ou des fonctionnalités qui ne sont pas destinées à être accessibles au public. Cela est particulièrement vrai pour les grandes bases de code avec de nombreux auteurs qui ne connaissent que superficiellement différents domaines.

Il existe des alternatives au spécificateur ami, mais souvent elles sont lourdes (classes concrètes au niveau cpp / typedefs masqués) ou non infaillibles (commentaires ou conventions de nom de fonction).

Sur la réponse;

Le friendspécificateur permet à la classe désignée d'accéder aux données ou fonctionnalités protégées au sein de la classe faisant la déclaration friend. Par exemple, dans le code ci-dessous, n'importe qui peut demander son nom à un enfant, mais seules la mère et l'enfant peuvent changer le nom.

Vous pouvez prendre cet exemple simple plus loin en considérant une classe plus complexe telle qu'une fenêtre. Il est très probable qu'une fenêtre aura de nombreux éléments de fonction / données qui ne devraient pas être accessibles au public, mais SONT nécessaires à une classe connexe telle qu'un WindowManager.

class Child
{
//Mother class members can access the private parts of class Child.
friend class Mother;

public:

  string name( void );

protected:

  void setName( string newName );
};

114
Comme note supplémentaire, la FAQ C ++ mentionne qui friend améliore l' encapsulation. friendaccorde un accès sélectif aux membres, tout comme le protectedfait. Tout contrôle fin est préférable à l'octroi d'un accès public. D'autres langages définissent également des mécanismes d'accès sélectif, pensez aux C # internal. La plupart des critiques négatives concernant l'utilisation de friendsont liées au couplage plus étroit, qui est généralement considéré comme une mauvaise chose. Cependant, dans certains cas, un couplage plus serré est précisément ce que vous voulez et friendvous donne cette puissance.
André Caron

5
Pourriez-vous s'il vous plaît en dire plus sur (classes concrètes de niveau cpp) et (typedefs masqués), Andrew ?
OmarOthman

18
Cette réponse semble être davantage axée sur l'explication de ce qu'est friendplutôt que sur un exemple motivant . L'exemple Window / WindowManager est meilleur que l'exemple illustré, mais trop vague. Cette réponse n'aborde pas non plus la partie encapsulation de la question.
bames53

4
Donc, «ami» existe effectivement parce que C ++ n'a aucune notion de package dans lequel tous les membres peuvent partager les détails d'implémentation? Je serais vraiment intéressé par un exemple du monde réel.
weberc2

1
Je pense que l'exemple mère / enfant est regrettable. Ces noms conviennent aux instances, mais pas aux classes. (Le problème montre si nous avons 2 mères et que chacune a son propre enfant).
Jo So

162

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

Il suffit de dire que je n'utiliserais pas le mot-clé ami comme élément essentiel de votre conception.


C'est exactement pour ça que je l'utilise. Cela ou définissez simplement les variables membres sur protected. C'est juste dommage que cela ne fonctionne pas pour C ++ / CLI :-(
Jon Cage

12
Personnellement, je découragerais cela. En général, vous testez une interface, c'est-à-dire qu'un ensemble d'entrées donne-t-il l'ensemble attendu de sortie (s). Pourquoi avez-vous besoin d'inspecter les données internes?
Graeme

55
@Graeme: Parce qu'un bon plan de test comprend à la fois des tests en boîte blanche et en boîte noire.
Ben Voigt

1
J'ai tendance à être d'accord avec @Graeme, comme expliqué parfaitement dans cette réponse .
Alexis Leclerc

2
@Graeme, il peut ne pas s'agir directement de données internes. Je peux être une méthode qui effectue une opération ou une tâche spécifique sur ces données lorsque cette méthode est privée pour la classe et ne doit pas être accessible au public tandis qu'un autre objet peut avoir besoin de nourrir ou d'amorcer la méthode protégée de cette classe avec ses propres données.
Francis Cugler

93

Le friendmot-clé a plusieurs bonnes utilisations. Voici les deux utilisations immédiatement visibles pour moi:

Définition d'ami

La définition de Friend permet de définir une fonction dans la portée de classe, mais la fonction ne sera pas définie comme une fonction membre, mais comme une fonction libre de l'espace de noms englobant, et ne sera pas visible normalement, sauf pour la recherche dépendante de l'argument. Cela le rend particulièrement utile pour la surcharge de l'opérateur:

namespace utils {
    class f {
    private:
        typedef int int_type;
        int_type value;

    public:
        // let's assume it doesn't only need .value, but some
        // internal stuff.
        friend f operator+(f const& a, f const& b) {
            // name resolution finds names in class-scope. 
            // int_type is visible here.
            return f(a.value + b.value);
        }

        int getValue() const { return value; }
    };
}

int main() {
    utils::f a, b;
    std::cout << (a + b).getValue(); // valid
}

Classe de base CRTP privée

Parfois, vous trouvez la nécessité pour une stratégie d'accéder à la classe dérivée:

// possible policy used for flexible-class.
template<typename Derived>
struct Policy {
    void doSomething() {
        // casting this to Derived* requires us to see that we are a 
        // base-class of Derived.
        some_type const& t = static_cast<Derived*>(this)->getSomething();
    }
};

// note, derived privately
template<template<typename> class SomePolicy>
struct FlexibleClass : private SomePolicy<FlexibleClass> {
    // we derive privately, so the base-class wouldn't notice that, 
    // (even though it's the base itself!), so we need a friend declaration
    // to make the base a friend of us.
    friend class SomePolicy<FlexibleClass>;

    void doStuff() {
         // calls doSomething of the policy
         this->doSomething();
    }

    // will return useful information
    some_type getSomething();
};

Vous trouverez un exemple non artificiel pour cela dans cette réponse. Un autre code utilisant cela est dans cette réponse. La base CRTP jette son ce pointeur, pour pouvoir accéder aux champs de données de la classe dérivée en utilisant des pointeurs de membres de données.


Salut, je reçois une erreur de syntaxe (dans xcode 4) lorsque j'essaie votre CRTP. Xcode pense que j'essaie d'hériter d'un modèle de classe. L'erreur se produit P<C>en template<template<typename> class P> class C : P<C> {};indiquant "L'utilisation du modèle de classe C nécessite des arguments de modèle". Avez-vous eu les mêmes problèmes ou connaissez-vous une solution?
bennedich

@bennedich à première vue, cela ressemble au type d'erreur que vous obtiendriez avec une prise en charge insuffisante des fonctionnalités C ++. Ce qui est assez courant chez les compilateurs. L'utilisation de l' FlexibleClassintérieur FlexibleClassdevrait implicitement faire référence à son propre type.
Yakk - Adam Nevraumont

@bennedich: Les règles d'utilisation du nom d'un modèle de classe à partir du corps de classe ont changé avec C ++ 11. Essayez d'activer le mode C ++ 11 dans votre compilateur.
Ben Voigt

Dans Visual Studio 2015, ajoutez ce public: f () {}; f (int_type t): valeur (t) {}; Pour éviter cette erreur du compilateur: erreur C2440: '<function-style-cast>': impossible de convertir de 'utils :: f :: int_type' en 'utils :: f' note: Aucun constructeur ne peut prendre le type source, ou constructeur résolution de surcharge était ambiguë
Damian

41

@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 interrompue que si cela pouvait être causé de l'extérieur de la classe, par exemple si vous operator <<proclamiez «Je suis un ami de classe foo».

friendremplace l'utilisation de public, pas l'utilisation de private!

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


14
"un ami remplace l'usage du public, pas celui du privé!", j'appuie cela
Waleed Eissa

26
@Assaf: oui, mais le FQA est, pour la plupart a, beaucoup de charabia incohérent en colère sans aucune valeur réelle. La partie friendne fait pas exception. La seule véritable observation ici est que C ++ assure l'encapsulation uniquement au moment de la compilation. Et vous n'avez plus besoin de mots pour le dire. Le reste est des conneries. Donc, en résumé: cette section de la FQA ne mérite pas d'être mentionnée.
Konrad Rudolph

12
La plupart de cette FQA est totalement blx :)
rama-jka toti

1
@Konrad: "La seule vraie observation ici est que C ++ assure l'encapsulation au moment de la compilation uniquement." Y a-t-il des langues qui assurent cela au moment de l'exécution? Pour autant que je sache, le renvoi de références à des membres privés (et des fonctions, pour les langages qui autorisent des pointeurs vers des fonctions ou des fonctions en tant qu'objets de première classe) est autorisé en C #, Java, Python et bien d'autres.
André Caron

@ André: la JVM et le CLR peuvent effectivement le garantir à ma connaissance. Je ne sais pas si c'est toujours fait mais vous pouvez prétendument protéger les packages / assemblys contre une telle intrusion (mais je ne l'ai jamais fait moi-même).
Konrad Rudolph

27

L'exemple canonique est de surcharger l'opérateur <<. Une autre utilisation courante consiste à autoriser un assistant ou une classe d'administration à accéder à vos éléments internes.

Voici quelques directives que j'ai entendues sur les amis C ++. Le dernier est particulièrement mémorable.

  • Vos amis ne sont pas les amis de votre enfant.
  • Les amis de votre enfant ne sont pas vos amis.
  • Seuls les amis peuvent toucher vos parties intimes.

" L'exemple canonique est de surcharger l'opérateur <<. " Le canonique de ne pas utiliser friendje suppose.
curiousguy

16

edit: Lire la faq un peu plus longtemps j'aime l'idée de l'opérateur << >> surcharger et ajouter comme ami de ces classes, mais je ne sais pas comment cela ne rompt pas l'encapsulation

Comment romprait-elle l'encapsulation?

Vous rompez 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;
};

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

c2est évidemment encapsulé. Il n'y a pas d'accès public à x. Tout ce que vous pouvez faire est d'appeler la foofonction, 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 à des fonctions inconnues?

Non. Il permet précisément à une fonction d'accéder aux membres privés de la classe. Tout comme l'a c2fait. Et tout comme c2, la seule fonction qui a accès n'est pas «une fonction inconnue aléatoire», mais «la fonction répertoriée dans la définition de 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 est-ce exactement moins encapsulé? La même quantité de code a accès aux membres privés de la classe. Et tous ceux qui y ont accès sont répertoriés dans la définition de classe.

friendne rompt pas l'encapsulation. Cela met certains programmeurs Java mal à l'aise, car lorsqu'ils disent "OOP", ils signifient en fait "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 n'a aucun sens pour plusieurs raisons .

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

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 précitée, est parfaitement encapsulé. Et pourtant, cela permet à quiconque de lire et de modifier x . Comment cela a-t-il même un sens? (indice: ce n'est pas le cas)

Conclusion: l'encapsulation consiste à pouvoir contrôler les fonctions pouvant accéder aux membres privés. Il ne s'agit pas précisément de l'emplacement des définitions de ces fonctions.


10

Une autre version courante de l'exemple d'Andrew, le redouté couplet de code

parent.addChild(child);
child.setParent(parent);

Au lieu de vous inquiéter si les deux lignes sont toujours effectuées ensemble et dans un ordre cohérent, vous pouvez rendre les méthodes privées et avoir une fonction amie pour appliquer la cohérence:

class Parent;

class Object {
private:
    void setParent(Parent&);

    friend void addChild(Parent& parent, Object& child);
};

class Parent : public Object {
private:
     void addChild(Object& child);

     friend void addChild(Parent& parent, Object& child);
};

void addChild(Parent& parent, Object& child) {
    if( &parent == &child ){ 
        wetPants(); 
    }
    parent.addChild(child);
    child.setParent(parent);
}

En d'autres termes, vous pouvez réduire la taille des interfaces publiques et appliquer des invariants qui traversent les classes et les objets dans les fonctions ami.


6
Pourquoi aurait-on besoin d'un ami pour ça? Pourquoi ne pas laisser la addChildfonction membre définir également le parent?
Nawaz

1
Un meilleur exemple serait de se faire setParentun ami, car vous ne voulez pas autoriser les clients à changer le parent puisque vous le gérerez dans la catégorie addChild/ removeChilddes fonctions.
Ylisar

8

Vous contrôlez les droits d'accès des membres et des fonctions en utilisant le droit Privé / Protégé / Public? donc en supposant que l'idée de chacun de ces 3 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 une sorte de «liste blanche», n'est-ce pas?

Alors, ami, vous avez la possibilité d'avoir une isolation solide contre les objets, mais permet de créer une "faille" pour les choses que vous jugez justifiées.

Je suppose que les gens disent que ce n'est pas nécessaire car il y a toujours un design qui s'en passera. 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 le moyen (presque) le plus élégant. .. Je pense que c'est le même cas avec des amis.

Cela ne fait pas vraiment de bien, à part vous permettre d'accéder à une variable membre sans utiliser une fonction de réglage

eh bien ce n'est pas exactement la façon de voir les choses. L’idée est de contrôler l’OMS qui peut accéder à quoi, ayant ou non fonction de réglage n'a pas grand-chose à voir avec cela.


2
Comment est friendune échappatoire? Il permet aux méthodes répertoriées dans la classe d' accéder à ses membres privés. Il ne laisse toujours pas de code arbitraire y accéder. En tant que tel, il n'est pas différent d'une fonction de membre public.
jalf

friend est aussi proche que possible de l'accès au niveau du package C # / Java en C ++. @jalf - qu'en est-il des classes d'amis (comme une classe d'usine)?
Ogre Psalm33

1
@Ogre: Et eux? Vous donnez toujours spécifiquement cette classe et personne d'autre accès aux internes de la classe. Vous ne laissez pas simplement la porte ouverte pour que du code inconnu arbitraire se dérobe à votre classe.
2010

8

J'ai trouvé un endroit pratique pour utiliser l'accès ami: la plus petite des fonctions privées.


Mais une fonction publique peut-elle également être utilisée pour cela? Quel est l'avantage d'utiliser l'accès ami?
Zheng Qu

@Maverobot Pourriez-vous développer votre question?
VladimirS

5

Friend est utile lorsque vous créez un conteneur et que vous souhaitez implémenter un itérateur pour cette classe.


4

Nous avons eu un problème intéressant dans une entreprise où je travaillais auparavant, où nous avons utilisé un ami pour un effet décent. J'ai travaillé dans notre service de cadre, nous avons créé un système de niveau moteur de base sur notre système d'exploitation personnalisé. En interne, nous avions une structure de classe:

         Game
        /    \
 TwoPlayer  SinglePlayer

Toutes ces classes faisaient partie du cadre et maintenues par notre équipe. Les jeux produits par la société ont été construits au-dessus de ce cadre dérivé d'un des enfants des Jeux. Le problème était que Game avait des interfaces avec diverses choses auxquelles SinglePlayer et TwoPlayer avaient besoin d'accéder mais que nous ne voulions pas exposer en dehors des classes de framework. La solution consistait à rendre ces interfaces privées et à permettre à TwoPlayer et SinglePlayer d'y accéder via l'amitié.

En vérité, tout ce problème aurait pu être résolu par une meilleure mise en œuvre de notre système, mais nous étions enfermés dans ce que nous avions.


4

La réponse courte serait: utilisez ami lorsque cela améliore réellement l' encapsulation. L'amélioration de la lisibilité et de l'utilisabilité (les opérateurs << et >> sont l'exemple canonique) est également une bonne raison.

En ce qui concerne les exemples d'amélioration de l'encapsulation, les classes spécialement conçues pour fonctionner avec les internes d'autres classes (les classes de test me viennent à l'esprit) sont de bons candidats.


"Les opérateurs << et >> sont l'exemple canonique " Non . Des contre-exemples plutôt canoniques .
curiousguy

@curiousguy: les opérateurs <<et >>sont généralement amis, au lieu de membres, car les rendre membres les rendrait difficiles à utiliser. Bien sûr, je parle du cas où ces opérateurs ont besoin d'accéder à des données privées; sinon, l'amitié est inutile.
Gorpik

" parce que les rendre membres les rendrait difficiles à utiliser. " Évidemment, faire operator<<et les operator>>membres de la classe de valeur au lieu de non-membres, ou membres de i|ostream, ne fourniraient pas la syntaxe souhaitée, et je ne le suggère pas. " Je parle du cas où ces opérateurs ont besoin d'accéder à des données privées " Je ne vois pas très bien pourquoi les opérateurs d'entrée / sortie auraient besoin d'accéder à des membres privés.
curiousguy

4

Le créateur de C ++ dit que cela ne rompt aucun principe d'encapsulation, et je vais le citer:

"Ami" viole-t-il l'encapsulation? Non. "Friend" est un mécanisme explicite pour accorder l'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.

C'est plus que clair ...


@curiousguy: Même dans le cas de modèles, c'est vrai.
Nawaz

L'amitié @Nawaz Template peut être accordée, mais n'importe qui peut effectuer une nouvelle spécialisation partielle ou explicite sans modifier la classe d'octroi d'amitié. Mais soyez prudent avec les violations ODR lorsque vous faites cela. Et ne fais pas ça de toute façon.
curiousguy

3

Une autre utilisation: ami (+ héritage virtuel) peut être utilisé pour éviter de dériver d'une classe (aka: "rendre une classe insécable") => 1 , 2

À partir de 2 :

 class Fred;

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

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

3

Pour faire TDD plusieurs fois, j'ai utilisé le mot-clé «ami» en C ++.

Un ami peut-il tout savoir sur moi?


Mise à jour: j'ai trouvé cette précieuse réponse sur le mot-clé "ami" du site Bjarne Stroustrup .

"Friend" est un mécanisme explicite pour accorder l'accès, tout comme l'adhésion.


3

Vous devez être très prudent quand / où vous utilisez le friendmot - clé et, comme vous, je l'ai utilisé très rarement. Voici quelques notes sur l'utilisation friendet les alternatives.

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

  • Utilisez des 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'accessoires, qui est (légèrement) plus lent que l'accès variable direct, plus difficile à lire et encombrant. Le problème avec la deuxième approche est que vous rompez complètement l'encapsulation.

Ce qui serait bien, c'est si nous pouvions définir une fonction externe qui pourrait toujours avoir accès aux membres privés d'une classe. Nous pouvons le faire avec le friendmot clé:

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

La méthode equal(Beer, Beer)a un accès direct maintenant aet bmembres privés »(qui peut être char *brand, float percentAlcoholetc. Ceci est un exemple plutôt arrangé, vous appliqueriez plus tôt friendà une surcharge == operator, mais nous y reviendrons.

Quelques points à noter:

  • A friendn'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 tout faire 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 ++ : "Ce n'est pas parce que je vous accorde un accès d'amitié que vos enfants m'accèdent automatiquement, n'accorde pas automatiquement l'accès à vos amis et ne m'autorise pas automatiquement . "

Je ne l'utilise vraiment que friendslorsqu'il est beaucoup plus difficile de le faire dans l'autre sens. Comme autre exemple, beaucoup de mathématiques vecteur fonctions sont souvent créés en friendsraison de l'interopérabilité Mat2x2, Mat3x3, Mat4x4, Vec2, Vec3, Vec4, etc. Et il est tellement plus facile d'être amis, plutôt que d' avoir à utiliser accesseurs partout. Comme indiqué, friendest souvent utile lorsqu'il est appliqué à <<(vraiment pratique pour le débogage), >>et peut-être à l' ==opérateur, mais peut également ê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 l'ai dit, je n'utilise pas du friendtout très souvent, mais de temps en temps c'est exactement ce dont vous avez besoin. J'espère que cela t'aides!


2

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

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 fonction de ces fonctions. Cela donne l'avantage supplémentaire de vous permettre de rendre ces fonctions virtuelles, ce qui permet une sérialisation virtuelle.


" En ce qui concerne l'opérateur << et l'opérateur >>, il n'y a aucune bonne raison de se faire des amis. " Absolument correct. « Cela donne l'avantage supplémentaire de vous permettre de rendre ces fonctions virtuelles ». Si la classe en question est destinée à être dérivée, oui. Sinon, pourquoi s'embêter?
curiousguy

Je ne comprends vraiment pas pourquoi cette réponse a été rejetée deux fois - et sans même une explication! C'est vulgaire.
curiousguy

virtuel ajouterait un hit de perf qui pourrait être assez important en sérialisation
paulm

2

J'utilise uniquement le mot-clé friend pour les fonctions protégées les plus complètes. Certains diront que vous ne devriez pas tester la fonctionnalité protégée. Je trouve cependant cet outil très utile lors de l'ajout de nouvelles fonctionnalités.

Cependant, je n'utilise pas le mot clé directement dans les déclarations de classe, j'utilise plutôt un astucieux modèle-hack pour y parvenir:

template<typename T>
class FriendIdentity {
public:
  typedef T me;
};

/**
 * A class to get access to protected stuff in unittests. Don't use
 * directly, use friendMe() instead.
 */
template<class ToFriend, typename ParentClass>
class Friender: public ParentClass
{
public:
  Friender() {}
  virtual ~Friender() {}
private:
// MSVC != GCC
#ifdef _MSC_VER
  friend ToFriend;
#else
  friend class FriendIdentity<ToFriend>::me;
#endif
};

/**
 * Gives access to protected variables/functions in unittests.
 * Usage: <code>friendMe(this, someprotectedobject).someProtectedMethod();</code>
 */
template<typename Tester, typename ParentClass>
Friender<Tester, ParentClass> & 
friendMe(Tester * me, ParentClass & instance)
{
    return (Friender<Tester, ParentClass> &)(instance);
}

Cela me permet de faire ce qui suit:

friendMe(this, someClassInstance).someProtectedFunction();

Fonctionne sur GCC et MSVC au moins.


2

En C ++, le mot-clé "friend" est utile dans la surcharge de l'opérateur et la création d'un pont.

1.) Mot-clé ami dans la surcharge d'opérateur:
Exemple de surcharge d'opérateur: Supposons que nous ayons une classe "Point" qui a deux variables flottantes
"x" (pour la coordonnée x) et "y" (pour la coordonnée y). Maintenant, nous devons surcharger "<<"(opérateur d'extraction) de telle sorte que si nous appelons"cout << pointobj" il affichera les coordonnées x et y (où pointobj est un objet de la classe Point). Pour ce faire, nous avons deux options:

   1.Surcharger la fonction "operator << ()" dans la classe "ostream".
   2.Overload "operator << ()" function in "Point" class.
Maintenant, la première option n'est pas bonne parce que si nous devons surcharger à nouveau cet opérateur pour une classe différente, nous devons à nouveau faire des changements dans la classe "ostream".
C'est pourquoi la seconde est la meilleure option. Le compilateur peut maintenant appeler la "operator <<()"fonction:

   1.Utilisation de l'objet ostream cout.As: cout.operator << (Pointobj) (forme ostream class). 
2.Appelez sans objet. Comme: opérateur << (cout, Pointobj) (de la classe Point).

Parce que nous avons implémenté la surcharge dans la classe Point. Donc, pour appeler cette fonction sans objet, nous devons ajouter un "friend"mot-clé car nous pouvons appeler une fonction amie sans objet. Maintenant, la déclaration de fonction sera As:
"friend ostream &operator<<(ostream &cout, Point &pointobj);"

2.) Mot-clé Friend dans la création d'un pont:
supposons que nous devons créer une fonction dans laquelle nous devons accéder à un membre privé de deux classes ou plus (généralement appelé "pont"). Comment faire:
pour accéder au membre privé d'une classe, il doit être membre de cette classe. Maintenant, pour accéder à un membre privé d'une autre classe, chaque classe doit déclarer cette fonction comme une fonction amie. Par exemple: Supposons qu'il existe deux classes A et B. Une fonction "funcBridge()"souhaite accéder au membre privé des deux classes. Ensuite, les deux classes doivent déclarer "funcBridge()":
friend return_type funcBridge(A &a_obj, B & b_obj);

Je pense que cela aiderait à comprendre le mot-clé ami.


2

Comme le dit la référence pour la déclaration d'ami :

La déclaration friend apparaît dans un corps de classe et accorde à une fonction ou à une autre classe l'accès aux membres privés et protégés de la classe où la déclaration friend apparaît.

Donc, juste pour rappel, il y a des erreurs techniques dans certaines des réponses qui disent que friendseuls les membres protégés peuvent visiter .


1

L'exemple d'arbre est un très bon exemple: avoir un objet implémenté dans quelques classes différentes sans avoir de relation d'héritage.

Peut-être pourriez-vous également en avoir besoin pour protéger un constructeur et forcer les gens à utiliser votre usine "ami".

... Ok, bien franchement vous pouvez vous en passer.


1

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

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


1

Une instance spécifique où j'utilise friendest lors de la création de classes Singleton . Le friendmot-clé me ​​permet de créer une fonction d'accesseur, qui est plus concise que d'avoir toujours une méthode "GetInstance ()" sur la classe.

/////////////////////////
// Header file
class MySingleton
{
private:
    // Private c-tor for Singleton pattern
    MySingleton() {}

    friend MySingleton& GetMySingleton();
}

// Accessor function - less verbose than having a "GetInstance()"
//   static function on the class
MySingleton& GetMySingleton();


/////////////////////////
// Implementation file
MySingleton& GetMySingleton()
{
    static MySingleton theInstance;
    return theInstance;
}

Cela peut être une question de goût, mais je ne pense pas que l'enregistrement de quelques touches justifie l'utilisation d'un ami ici. GetMySingleton () doit être une méthode statique de la classe.
Gorpik

Le c-tor privé interdirait une fonction non-ami pour instancier MySingleton, donc le mot-clé friend est nécessaire ici.
JBRWilkinson

@Gorpik " Cela peut être une question de goût, mais je ne pense pas que l'enregistrement de quelques touches justifie l'utilisation d'un ami ici. " C'est le cas. Quoi qu'il en soit, friendn'a pas besoin d'une "justification" particulière , contrairement à l'ajout d'une fonction membre.
curiousguy

Les singletons sont de toute façon considérés comme une mauvaise pratique (Google "singleton nocif" et vous obtiendrez beaucoup de résultats comme celui-ci . Je ne pense pas que l'utilisation d'une fonctionnalité pour implémenter un
anti

1

Les fonctions et classes Friend fournissent un accès direct aux membres privés et protégés de la classe pour éviter de briser l'encapsulation dans le cas général. La plupart des utilisations se font 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, nous définissons donc l'opérateur surchargé

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

Il y a cependant des implications évidentes d'encapsulation. Tout d'abord, la classe ou la fonction ami a désormais un accès complet à 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 désormais imbriqué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, ce n'est pas un problème, logiquement. Mais, dans ce cas, pourquoi était-il nécessaire de spearer l'ami en premier lieu.

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

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 rompue, la classe B n'a pas accès à l'implémentation interne dans A, mais le résultat est le même que si nous avions déclaré B ami de A. Le compilateur optimisera les appels de fonction, ce qui entraînera la même chose instructions comme accès direct.

Je pense que l'utilisation de «ami» est simplement un raccourci avec des avantages discutables, mais un coût certain.


1

Il ne s'agit peut-être pas d'une situation d'utilisation réelle, mais cela peut aider à illustrer l'utilisation d'amis entre les classes.

The ClubHouse

class ClubHouse {
public:
    friend class VIPMember; // VIP Members Have Full Access To Class
private:
    unsigned nonMembers_;
    unsigned paidMembers_;
    unsigned vipMembers;

    std::vector<Member> members_;
public:
    ClubHouse() : nonMembers_(0), paidMembers_(0), vipMembers(0) {}

    addMember( const Member& member ) { // ...code }   
    void updateMembership( unsigned memberID, Member::MembershipType type ) { // ...code }
    Amenity getAmenity( unsigned memberID ) { // ...code }

protected:
    void joinVIPEvent( unsigned memberID ) { // ...code }

}; // ClubHouse

La classe des membres

class Member {
public:
    enum MemberShipType {
        NON_MEMBER_PAID_EVENT,   // Single Event Paid (At Door)
        PAID_MEMBERSHIP,         // Monthly - Yearly Subscription
        VIP_MEMBERSHIP,          // Highest Possible Membership
    }; // MemberShipType

protected:
    MemberShipType type_;
    unsigned id_;
    Amenity amenity_;
public:
    Member( unsigned id, MemberShipType type ) : id_(id), type_(type) {}
    virtual ~Member(){}
    unsigned getId() const { return id_; }
    MemberShipType getType() const { return type_; }
    virtual void getAmenityFromClubHouse() = 0       
};

class NonMember : public Member {
public:
   explicit NonMember( unsigned id ) : Member( id, MemberShipType::NON_MEMBER_PAID_EVENT ) {}   

   void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }
};

class PaidMember : public Member {
public:
    explicit PaidMember( unsigned id ) : Member( id, MemberShipType::PAID_MEMBERSHIP ) {}

    void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }
};

class VIPMember : public Member {
public:
    friend class ClubHouse;
public:
    explicit VIPMember( unsigned id ) : Member( id, MemberShipType::VIP_MEMBERSHIP ) {}

    void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }

    void attendVIPEvent() {
        ClubHouse::joinVIPEvent( this->id );
    }
};

Agréments

class Amenity{};

Si vous regardez la relation de ces classes ici; le ClubHouse détient une variété de différents types d'adhésions et d'accès aux membres. Les membres sont tous dérivés d'une classe super ou de base car ils partagent tous un ID et un type énuméré qui sont des classes communes et externes peuvent accéder à leurs ID et types via des fonctions d'accès qui se trouvent dans la classe de base.

Cependant, à travers ce type de hiérarchie des membres et de ses classes dérivées et de leur relation avec la classe ClubHouse, la seule classe dérivée qui a des "privilèges spéciaux" est la classe VIPMember. La classe de base et les 2 autres classes dérivées ne peuvent pas accéder à la méthode joinVIPEvent () de ClubHouse, mais la classe Membre VIP a ce privilège comme si elle avait un accès complet à cet événement.

Ainsi, avec le VIPMember et le ClubHouse, il s'agit d'une rue d'accès à double sens où les autres classes membres sont limitées.


0

Lors de l'implémentation d'algorithmes d'arbre pour la classe, le code cadre que le prof nous a donné avait la classe d'arbre en tant qu'ami de la classe de noeud.

Cela ne fait pas vraiment de bien, à part vous permettre d'accéder à une variable membre sans utiliser de fonction de réglage.


0

Vous pouvez utiliser l'amitié lorsque différentes classes (n'héritant pas l'une de l'autre) utilisent des membres privés ou protégés de l'autre classe.

Les cas d'utilisation typiques des fonctions ami sont des opérations qui sont effectuées entre deux classes différentes accédant à des membres privés ou protégés des deux.

sur http://www.cplusplus.com/doc/tutorial/inheritance/ .

Vous pouvez voir cet exemple où la méthode non membre accède aux membres privés d'une classe. Cette méthode doit être déclarée dans cette même classe en tant qu'ami de la classe.

// friend functions
#include <iostream>
using namespace std;

class Rectangle {
    int width, height;
  public:
    Rectangle() {}
    Rectangle (int x, int y) : width(x), height(y) {}
    int area() {return width * height;}
    friend Rectangle duplicate (const Rectangle&);
};

Rectangle duplicate (const Rectangle& param)
{
  Rectangle res;
  res.width = param.width*2;
  res.height = param.height*2;
  return res;
}

int main () {
  Rectangle foo;
  Rectangle bar (2,3);
  foo = duplicate (bar);
  cout << foo.area() << '\n';
  return 0;
}

0

J'ai probablement manqué quelque chose dans les réponses ci-dessus, mais un autre concept important dans l'encapsulation cache la mise en œuvre. La réduction de l'accès aux données privées membres (les détails d'implémentation d'une classe) permet une modification beaucoup plus facile du code ultérieurement. Si un ami accède directement aux données privées, toute modification des champs de données d'implémentation (données privées), casse le code d'accès à ces données. L'utilisation de méthodes d'accès élimine principalement cela. Assez important, je pense.


-1

Vous pouvez adhérer aux principes de POO les plus stricts et les plus purs et vous assurer qu'aucun membre de données pour aucune classe n'a même des accesseurs de sorte que tous les objets doivent être les seuls à pouvoir connaître leurs données avec la seule façon d'agir sur eux via des messages indirects , c'est-à-dire les méthodes.

Mais même C # a un mot-clé de visibilité interne et Java a son accessibilité au niveau du package par défaut pour certaines choses. C ++ se rapproche en fait de l'idéal OOP en minimisant le compromis de visibilité dans une classe en spécifiant exactement quelle autre classe et seulement d' autres classes pourraient y voir.

Je n'utilise pas vraiment C ++ mais si C # avait des amis, je le ferais au lieu du modificateur interne global d'assemblage , que j'utilise beaucoup. Cela ne rompt pas vraiment l'incapsulation, car l'unité de déploiement dans .NET est un assemblage.

Mais il y a ensuite l' attribut InternalsVisibleTo (otherAssembly) qui agit comme un mécanisme ami de l' assemblage croisé . Microsoft l'utilise pour les assemblages de concepteurs visuels .


-1

Les amis sont également utiles pour les rappels. Vous pouvez implémenter des rappels en tant que méthodes statiques

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

où les callbackappels en localCallbackinterne, et le clientDatacontient votre instance. À mon avis,

ou...

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

Cela permet à l'ami d'être défini uniquement dans le cpp en tant que fonction de style c, et non d'encombrer la classe.

De même, un modèle que j'ai vu très souvent est de mettre tous les membres vraiment privés d'une classe dans une autre classe, qui est déclarée dans l'en-tête, définie dans le cpp et friended. Cela permet au codeur de cacher une grande partie de la complexité et du fonctionnement interne de la classe à l'utilisateur de l'en-tête.

Dans l'en-tête:

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

Dans le cpp,

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

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

Il devient plus facile de cacher des choses que l'aval n'a pas besoin de voir de cette façon.


1
Les interfaces ne seraient-elles pas un moyen plus propre d'y parvenir? Qu'est-ce qui empêche quelqu'un de rechercher MyFooPrivate.h?
JBRWilkinson

1
Eh bien, si vous utilisez le privé et le public pour garder des secrets, vous allez être facilement vaincu. Par "cacher", je veux dire, l'utilisateur de MyFoo n'a pas vraiment besoin de voir les membres privés. En plus de cela, il est utile de maintenir la compatibilité ABI. Si vous faites de _private un pointeur, l'implémentation privée peut changer autant que vous le souhaitez, sans toucher à l'interface publique, conservant ainsi la compatibilité ABI.
shash

Vous faites référence à l'idiome PIMPL; l'objectif pour lequel n'est pas une encapsulation supplémentaire comme vous semblez le dire, mais pour déplacer les détails de l'implémentation hors de l'en-tête afin de changer un détail d'implémentation ne force pas une recompilation du code client. De plus, il n'est pas nécessaire d'utiliser friend pour implémenter cet idiome.
weberc2

Hé bien oui. Son objectif principal est de déplacer les détails de mise en œuvre. L'ami là-bas est utile pour gérer les membres privés à l'intérieur de la classe publique depuis le privé ou l'inverse.
shash
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.