web-dev-qa-db-fra.com

accéder à un membre protégé d'une classe de base dans une autre sous-classe

Pourquoi cela compile-t-il:

class FooBase
{
protected:
    void fooBase(void);
};

class Foo : public FooBase
{
public:
    void foo(Foo& fooBar)
    {
        fooBar.fooBase();
    }
};

mais ce n'est pas?

class FooBase
{
protected:
    void fooBase(void);
};

class Foo : public FooBase
{
public:
    void foo(FooBase& fooBar)
    {
        fooBar.fooBase();
    }
};

D'une part, C++ autorise l'accès aux membres privés/protégés pour toutes les instances de cette classe, mais d'autre part, il n'accorde pas l'accès aux membres protégés d'une classe de base pour toutes les instances d'une sous-classe ..__ pour moi.

J'ai testé la compilation avec VC++ et avec ideone.com et les deux compilent le premier extrait de code, mais pas le second.

34
Kaiserludi

Lorsque foo reçoit une référence FooBase, le compilateur ne sait pas si l'argument est un descendant de Foo. Il doit donc supposer que ce n'est pas le cas. Foo a accès aux membres protégés hérités de autres objets Foo, pas à toutes les autres classes sœurs.

Considérons ce code:

class FooSibling: public FooBase { };

FooSibling sib;
Foo f;
f.foo(sib); // calls sib.fooBase()!?

Si Foo::foo peut appeler des membres protégés de descendants FooBase arbitraires, il peut alors appeler la méthode protégée de FooSibling, qui n'a pas de relation directe avec Foo. Ce n'est pas ainsi que l'accès protégé est censé fonctionner.

Si Foo a besoin d'accéder aux membres protégés de tous les objets FooBase, et pas seulement de ceux connus pour être des descendants Foo, alors Foo doit être un ami de FooBase:

class FooBase
{
protected:
  void fooBase(void);
  friend class Foo;
};
30
Rob Kennedy

Le C++ FAQ résume bien ce problème:

[Vous] avez le droit de choisir vos propres poches, mais pas celles de votre père ni de celles de votre frère.

20
h0b0

Le point clé est que protected vous donne accès à votre propre copie du membre, et non aux membres de any other object. Ceci est une idée fausse commune, car le plus souvent nous généralisons et déclarons que protected accorde l'accès au membre au type dérivé (sans indiquer explicitement que ce n'est que sur leurs propres bases ...)

Maintenant, c'est pour une raison et, en général, vous ne devriez pas accéder au membre d'une autre branche de la hiérarchie, car vous risqueriez de casser les invariants dont dépendent d'autres objets. Prenons un type qui effectue un calcul coûteux sur un membre de données volumineux (protégé) et deux types dérivés qui mettent en cache le résultat en appliquant différentes stratégies:

class base {
protected:
   LargeData data;
// ...
public:
   virtual int result() const;      // expensive calculation
   virtual void modify();           // modifies data
};
class cache_on_read : base {
private:
   mutable bool cached;
   mutable int cache_value;
// ...
   virtual int result() const {
       if (cached) return cache_value;
       cache_value = base::result();
       cached = true;
   }
   virtual void modify() {
       cached = false;
       base::modify();
   }
};
class cache_on_write : base {
   int result_value;
   virtual int result() const {
      return result_value;
   }
   virtual void modify() {
      base::modify();
      result_value = base::result(); 
   }
};

Le type cache_on_read capture les modifications apportées aux données et marque le résultat comme invalide, de sorte que le prochain read de la valeur soit recalculé. C’est une bonne approche si le nombre d’écritures est relativement élevé, car nous effectuons uniquement le calcul à la demande (c’est-à-dire que des modifications multiples ne déclencheront pas de recalcul). Le cache_on_write précalcule le résultat à l’avance, ce qui peut constituer une bonne stratégie si le nombre d’écritures est faible et que vous souhaitez des coûts déterministes pour la lecture (pensez à une faible latence pour les lectures).

Revenons maintenant au problème initial. Les deux stratégies de cache maintiennent un ensemble d'invariants plus strict que la base. Dans le premier cas, l'invariant supplémentaire est que cached est true uniquement si data n'a pas été modifié après la dernière lecture. Dans le deuxième cas, l'invariant supplémentaire est que result_value est la valeur de l'opération à tout moment.

Si un troisième type dérivé prenait une référence à base et accédait à data pour écrire (si protected le lui permettait), il romprait avec les invariants des types dérivés.

Cela étant dit, la spécification de la langue est cassée (opinion personnelle) car elle laisse une porte dérobée pour atteindre ce résultat particulier. En particulier, si vous créez un pointeur sur le membre d'un membre à partir d'une base dans un type dérivé, l'accès est vérifié dans derived, mais le pointeur renvoyé est un pointeur sur le membre de base, qui peut être appliqué à any base. objet:

class base {
protected:
   int x;
};
struct derived : base {
   static void modify( base& b ) {
      // b.x = 5;                        // error!
      b.*(&derived::x) = 5;              // allowed ?!?!?!
   }
}

Dans les deux exemples, Foo hérite d'une méthode protégée fooBase. Cependant, dans votre premier exemple, vous essayez d'accéder à la méthode protégée donnée à partir de la même classe (Foo::foo appelle Foo::fooBase), tandis que dans le second exemple, vous essayez d'accéder à une méthode protégée à partir d'une autre classe qui n'est pas déclarée comme classe amie (Foo::foo essaye pour appeler FooBase::fooBase, qui échoue, le dernier est protégé).

3
Zeta

Dans le premier exemple, vous passez un objet de type Foo, qui hérite évidemment de la méthode fooBase () et est donc capable de l'appeler. Dans le deuxième exemple, vous essayez simplement d'appeler une fonction protégée, quel que soit le contexte dans lequel vous ne pouvez pas appeler une fonction protégée à partir d'une instance de classe où sa déclarée so . , et vous avez donc le droit de l'appeler dans le contexte Foo

1
Moataz Elmasry

J'ai tendance à voir les choses en termes de concepts et de messages. Si votre méthode FooBase s'appelait réellement "SendMessage" et que Foo était "EnglishSpeakingPerson" et que FooBase était SpeakingPerson, votre déclaration protected est destinée à restreindre SendMessage entre EnglishSpeakingPersons (et sous-classes, par exemple: AmericanEnglishSpeakingPerson, AustralianEnglishSpeakingPerson). Un autre type FrenchSpeakingPerson dérivé de SpeakingPerson ne pourrait pas recevoir un SendMessage, sauf si vous déclariez FrenchSpeakingPerson en tant qu'ami, où "ami" signifiait que FrenchSpeakingPerson avait la capacité spéciale de recevoir SendMessage de EnglishSpeakingPerson (c'est-à-dire qu'il pouvait comprendre l'anglais).

1
Sentinel

En plus de la réponse de hobo vous pouvez rechercher une solution de contournement.

Si vous voulez que les sous-classes veuillent appeler la méthode fooBase, vous pouvez le rendre static. les méthodes protégées statiques sont accessibles par sous-classes avec tous les arguments.

0
yairchu