l'accès à un membre protégé d'une classe de base dans une autre sous-classe
pourquoi cette compilation:
class FooBase
{
protected:
void fooBase(void);
};
class Foo : public FooBase
{
public:
void foo(Foo& fooBar)
{
fooBar.fooBase();
}
};
mais cela ne fonctionne pas?
class FooBase
{
protected:
void fooBase(void);
};
class Foo : public FooBase
{
public:
void foo(FooBase& fooBar)
{
fooBar.fooBase();
}
};
d'une part C++ accorde l'accès aux membres privés/protégés pour toutes les instances de cette classe, mais d'autre part il ne donne pas l'accès aux membres protégés d'une classe de base pour toutes les instances d'une sous-classe. Cela me semble assez incohérent.
j'ai testé la compilation avec VC++ et avec ideone.com et les deux compilent le premier mais pas le second code.
7 réponses
Quand foo
reçoit un FooBase
référence, le compilateur ne sait pas si l'argument est un descendant de Foo
, donc il doit supposer que ce n'est pas le cas. Foo
a accès aux membres protégés hérités de autres Foo
objets, pas tous les autres cours.
réfléchir à 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 l'arbitraire FooBase
descendants, puis il peut appeler la méthode protégée de FooSibling
, qui n'a pas de en relation directe avec les Foo
. Ce n'est pas comment protéger l'accès est censé fonctionner.
Si Foo
a besoin d'accéder aux membres protégés de tous FooBase
les objets, pas seulement de ceux qui sont également connus pour être Foo
descendants, puisFoo
doit être un ami de FooBase
:
class FooBase
{
protected:
void fooBase(void);
friend class Foo;
};
Le point clé est que protected
vous accorde l'accès à votre propre copie du Membre, pas à ces membres en autre objet. Il s'agit là d'une erreur courante, car le plus souvent nous généralisons et disons protected
autorise l'accès au membre le type dérivé (sans indiquer explicitement que seulement à leurs propres bases...)
maintenant, C'est pour une raison, et en général vous ne devriez pas accéder au membre dans une branche différente de la hiérarchie, comme vous pourriez cassez les invariants dont dépendent les autres objets. Considérons un type qui effectue un calcul coûteux sur un grand membre de données (protégé) et deux types dérivés qui cache le résultat suivant 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();
}
};
cache_on_read
type capture les modifications aux données et marque le résultat comme non valide, de sorte que le prochain lire de la valeur recalcule. C'est une bonne approche si le nombre d'écritures est relativement élevé, comme nous l'avons seulement d'effectuer la calcul à la demande (c.-à-d. que les modifications multiples ne déclencheront pas de nouveaux calculs). cache_on_write
calcule le résultat à l'avance, ce qui pourrait être une bonne stratégie si le nombre d'Écritures est faible, et vous voulez des coûts déterministes pour la lecture (pensez à une faible latence sur les lectures).
Maintenant, revenons au problème initial. Les deux stratégies de cache maintiennent un ensemble plus strict d'invariants que la base. Dans le premier cas, l'invariant supplémentaire est que cached
true
seulement si data
n'a pas été modifié après la dernière lecture. Dans le second 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é a pris une référence à un base
et accessible data
écrire (si protected
l'autoriserait à), alors il se briserait avec les invariants des types dérivés.
cela étant dit, la spécification de la langue est cassé (avis personnel) qu'il laisse une porte dérobée pour atteindre ce résultat. En particulier, si vous créez un pointeur de membre d'un membre à partir d'une base dans un type dérivé, l'accès est contrôlé par derived
, mais le pointeur retourné est un pointeur de membre de l' base
, qui peut être appliqué à 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 aux données protégées méthode de la même classe (Foo::foo
appelle Foo::fooBase
), alors que dans le second exemple vous essayez d'accéder à une méthode protégée d'une autre classe qui n'est pas déclarée comme classe ami (Foo::foo
essaie de l'appeler FooBase::fooBase
, qui échoue, la dernière est protégée).
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 d'appeler une fonction protégée, simplement ainsi, quel que soit le contexte dans lequel vous ne pouvez pas appeler une fonction protégée à partir d'une instance de classe où elle est déclarée ainsi. Dans le premier exemple, vous héritez de la méthode protégée fooBase, et vous avez donc le droit de l'appeler dans le contexte Foo
j'ai tendance à voir les choses en termes de concepts et de messages. Si votre méthode de FooBase s'appelait en fait "SendMessage" et Foo était "English Speakingperson" et FooBase était SpeakingPerson, votre protégé
hobo réponse vous pouvez chercher une solution de contournement.
si vous voulez que les sous-classes appellent fooBase
méthode, vous pouvez le faire static
. les méthodes statiques protégées sont accessibles par des sous-classes avec tous les arguments.