web-dev-qa-db-fra.com

Pourquoi le C++ n'autorise-t-il pas l'amitié héritée?

Pourquoi l’amitié n’est-elle pas au moins facultativement héritable en C++? Je comprends que la transitivité et la réflexivité soient interdites pour des raisons évidentes (je ne le dis que pour éviter les réponses simplesFAQ citation), mais le manque de quelque chose du genre virtual friend class Foo; m'énerve. Est-ce que quelqu'un connaît le contexte historique derrière cette décision? L’amitié était-elle vraiment juste un hack limité qui a depuis trouvé sa place dans quelques obscurs usages respectables?

Modifiez pour clarifier: Je parle du scénario suivant non dans lequel les enfants de A sont exposés à B ou à B et à ses enfants. Je peux aussi imaginer éventuellement accorder l’accès aux substitutions de fonctions d’amis, etc.

class A {
  int x;
  friend class B;
};

class B {
  // OK as per friend declaration above.
  void foo(A& a, int n) { a.x = n; }
};

class D : public B { /* can't get in A w/o 'friend class D' declaration. */ };

Réponse acceptée: comme Loki indique , l'effet peut être plus ou moins simulé en créant des fonctions de proxy protégées dans des classes de base simplifiées, de sorte qu'il n'y a pas de nécessité stricte d'attribuer une amitié à un classe ou méthode virtuelle hiérarchie. Je n'aime pas la nécessité de recourir à des mandataires passe-partout (ce que la base simplifiée devient effectivement), mais je suppose que cela a été jugé préférable à un mécanisme de langage qui serait plus probablement mal utilisé la plupart du temps. Je pense qu'il est probablement temps d'acheter et de lire Stroupstrup La conception et l'évolution de C++ , que suffisamment de personnes ici recommandent, pour avoir une meilleure idée de ce type de questions ...

83
Jeff

Parce que je peux écrire Foo et son ami Bar (il existe donc une relation de confiance). 

Mais est-ce que je fais confiance aux personnes qui écrivent des classes dérivées de Bar?
Pas vraiment. Donc, ils ne devraient pas hériter de l'amitié.

Tout changement dans la représentation interne d'une classe nécessitera une modification de tout ce qui dépend de cette représentation. Ainsi, tous les membres d'une classe, ainsi que tous les amis de la classe, devront être modifiés. 

Par conséquent, si la représentation interne de Foo est modifiée, alors Bar doit également l'être (car l'amitié lie étroitement Bar à Foo). Si l'amitié était héritée, toutes les classes dérivées de Bar seraient aussi étroitement liées à Foo et nécessiteraient donc une modification si la représentation interne de Foo est modifiée. Mais je ne connais pas les types dérivés (je ne devrais pas non plus en avoir. Ils peuvent même être développés par différentes sociétés, etc.). Ainsi, je serais incapable de changer Foo car cela introduirait des modifications de rupture dans la base de code (car je ne pouvais pas modifier toutes les classes dérivées de Bar).

Ainsi, si l'amitié a été héritée, vous introduisez par inadvertance une restriction à la capacité de modifier une classe. Cela n’est pas souhaitable, car vous rendez pratiquement inutile le concept d’une API publique.

Remarque: Un enfant de Bar peut accéder à Foo à l'aide de Bar. Créez simplement la méthode dans Bar protected. Ensuite, l'enfant de Bar peut accéder à Foo en appelant par l'intermédiaire de sa classe parent.

C'est ce que tu veux?

class A
{
    int x;
    friend class B;
};

class B
{
    protected:
       // Now children of B can access foo
       void foo(A& a, int n) { a.x = n; }
};

class D : public B
{
    public:
        foo(A& a, int n)
        {
            B::foo(a, n + 5);
        }
};
79
Martin York

Pourquoi l’amitié n’est-elle pas au moins facultativement héritable en C++?

Je pense que la réponse à votre première question se trouve dans cette question: "Les amis de votre père ont-ils accès à vos contacts?"

40
wilx

Une classe amie peut exposer son ami par le biais de fonctions d’accesseur, puis en autoriser l’accès.

class stingy {
    int pennies;
    friend class hot_girl;
};

class hot_girl {
public:
    stingy *bf;

    int &get_cash( stingy &x = *bf ) { return x.pennies; }
};

class moocher {
public: // moocher can access stingy's pennies despite not being a friend
    int &get_cash( hot_girl &x ) { return x.get_cash(); }
};

Cela permet un contrôle plus fin que la transitivité optionnelle. Par exemple, get_cash peut être protected ou peut imposer un protocole d'accès limité à l'exécution.

8
Potatoswatter

Norme C++, section 11.4/8

L'amitié n'est ni héritée ni transitive.

Si l'amitié devait être héritée, une classe qui n'était pas censée être un ami aurait soudainement accès aux ressources internes de votre classe et violerait l'encapsulation.

7
David

Parce que c'est juste inutile.

L'utilisation du mot clé friend est en soi suspecte. En termes de couplage, c'est la pire relation (en avance sur l'héritage et la composition).

Toute modification apportée aux éléments internes d'une classe peut avoir un impact sur les amis de cette classe ... voulez-vous vraiment un nombre inconnu d'amis? Vous ne seriez même pas capable de les lister si ceux qui en héritaient pouvaient aussi être amis, et vous courriez le risque de casser le code de vos clients à chaque fois, ce n'est sûrement pas souhaitable.

J'admets volontiers que, pour les projets de devoirs ou d'animaux domestiques, la dépendance est souvent une considération lointaine. Peu importe les projets de petite taille. Mais dès que plusieurs personnes travaillent sur le même projet et que des dizaines de milliers de lignes vous sont nécessaires, vous devez limiter l'impact des modifications.

Ceci apporte une règle très simple:

Changer les éléments internes d'une classe ne devrait affecter que la classe elle-même

Bien sûr, vous allez probablement affecter ses amis, mais il y a deux cas ici:

  • fonction sans amis: probablement plus d'un membre fonctionnant de toute façon (je pense que je suis std::ostream& operator<<(...) ici, ce qui n'est pas un membre du seul fait des règles linguistiques
  • classe d'ami? vous n'avez pas besoin de cours d'amis sur de vrais cours.

Je recommanderais l'utilisation de la méthode simple:

class Example;

class ExampleKey { friend class Example; ExampleKey(); };

class Restricted
{
public:
  void forExampleOnly(int,int,ExampleKey const&);
};

Ce modèle Key simple vous permet de déclarer un ami (en quelque sorte) sans lui donner réellement accès à vos éléments internes, l’isolant ainsi des modifications. De plus, il permet à cet ami de prêter sa clé à des administrateurs (comme des enfants) si nécessaire.

2
Matthieu M.

Logique simple: «J'ai une amie Jane. Ce n'est pas parce que nous sommes devenus amis hier que tous ses amis sont à moi. 

Je dois encore approuver ces amitiés individuelles, et le niveau de confiance serait en conséquence.

0
Robert Hamm

La fonction Friend dans une classe attribue la propriété extern à la fonction. c'est-à-dire extern signifie que la fonction a été déclarée et définie quelque part en dehors de la classe. 

Par conséquent, cela signifie que la fonction ami n'est pas un membre d'une classe. Ainsi, l'héritage vous permet uniquement d'hériter des propriétés d'une classe, pas d'éléments externes. Et également si l'héritage est autorisé pour les fonctions ami, une classe tierce héritera. 

0
abdul rizwan

Une classe dérivée ne peut hériter que de quelque chose, qui est "membre" de la base. Une déclaration d'ami estpasun membre de la classe d'amitiés.

$ 11.4/1- "... Le nom d'un ami est Ne fait pas partie de la classe, et l'ami N'est appelé avec le membre Opérateurs d'accès (5.2.5) à moins qu'il ne soit un membre d'une autre classe. "

$ 11.4 - "De plus, étant donné que la clause de base De la classe friend ne fait pas partie de ses déclarations de membre , La clause de base De la classe friend ne peut pas accéder aux noms Du privé et protégés membres de la classe accordant amitié. "

et plus loin

$ 10.3/7- "[Remarque: le spécificateur virtuel Implique l’appartenance. Une fonction Virtuelle ne peut donc pas être une fonction non membre (7.1.2) Ni une fonction virtuelle Être un membre statique. , car l’appel d’une fonction virtuelle repose sur un objet spécifique pour déterminer quelle fonction appeler. Une fonction virtuelle déclarée dans une classe peut être déclarée comme un ami dans une autre classe.] "

Puisque le 'ami' n'est pas un membre de la classe de base en premier lieu, comment peut-il être hérité par la classe dérivée?

0
Chubsdad

Friend est bon en héritage comme interface de style pour conteneur Mais pour moi, comme le premier le dit, C++ manque d'héritage propageable

class Thing;

//an interface for Thing container's
struct IThing {
   friend Thing;
   protected:
       int IThing_getData() = 0;
};

//container for thing's
struct MyContainer : public IThing {
    protected: //here is reserved access to Thing
         int IThing_getData() override {...}
};

struct Thing {
    void setYourContainer(IThing* aContainerOfThings) {
        //access to unique function in protected area 
        aContainerOfThings->IThing_getData(); //authorized access
    }
};

struct ChildThing : public Thing {
    void doTest() {
        //here the lack of granularity, you cannot access to the container.
        //to use the container, you must implement all 
        //function in the Thing class
        aContainerOfThings->IThing_getData(); //forbidden access
    }
};

Pour moi, le problème du C++ est le manque de très bonne granularité pour contrôler tous les accès depuis n'importe où:

friend Thing peut devenir ami Thing. * pour donner accès à tous les enfants de Thing 

Et plus encore, ami [zone nommée] Thing. * Pour accorder l’accès à une Précise sont dans la classe Container via une zone nommée spéciale pour l’ami.

Ok arrête le rêve. Mais maintenant, vous connaissez un usage intéressant d’ami.

Dans un autre ordre, vous pouvez également trouver intéressant de connaître toutes les classes sont amicales avec soi-même. En d'autres mots, une instance de classe peut appeler tous
membres d'une autre instance du même nom sans restriction:

class Object {
     private:
         void test() {}
     protected:
         void callAnotherTest(Object* anotherObject) {
             //private, but yes you can call test() from 
             //another object instance
             anotherObject)->test(); 
         }
};
0
zep

Une hypothèse: si une classe déclare une autre classe/fonction en tant qu'ami, c'est parce que cette seconde entité a besoin d'un accès privilégié à la première. A quoi sert-il d’accorder à la deuxième entité un accès privilégié à un nombre arbitraire de classes dérivées de la première?

0
Oliver Charlesworth