c++ - programacion - acceder a un miembro protegido de una clase base en otra subclase
polimorfismo java (7)
¿Por qué esta compilación?
class FooBase
{
protected:
void fooBase(void);
};
class Foo : public FooBase
{
public:
void foo(Foo& fooBar)
{
fooBar.fooBase();
}
};
pero esto no?
class FooBase
{
protected:
void fooBase(void);
};
class Foo : public FooBase
{
public:
void foo(FooBase& fooBar)
{
fooBar.fooBase();
}
};
Por un lado, C ++ otorga acceso a miembros privados / protegidos para todas las instancias de esa clase, pero, por otro lado, no otorga acceso a miembros protegidos de una clase base para todas las instancias de una subclase. Esto me parece bastante inconsistente.
He probado la compilación con VC ++ y con ideone.com y ambos compilan el primer fragmento de código, pero no el segundo.
Además de la respuesta de vagabundo, puede buscar una solución alternativa.
Si desea que las subclases quieran llamar al método fooBase
, puede hacerlo static
. los métodos protegidos estáticos son accesibles por subclases con todos los argumentos.
Cuando foo
recibe una referencia de FooBase
, el compilador no sabe si el argumento es un descendiente de Foo
, por lo que tiene que asumir que no lo es. Foo
tiene acceso a miembros protegidos heredados de otros objetos de Foo
, no a todas las otras clases de hermanos.
Considera este código:
class FooSibling: public FooBase { };
FooSibling sib;
Foo f;
f.foo(sib); // calls sib.fooBase()!?
Si Foo::foo
puede llamar a miembros protegidos de descendientes FooBase
arbitrarios, puede llamar al método protegido de FooSibling
, que no tiene relación directa con Foo
. No es así como se supone que el acceso protegido funciona.
Si Foo
necesita acceso a los miembros protegidos de todos los objetos FooBase
, no solo aquellos que también se sabe que son descendientes de Foo
, entonces Foo
necesita ser un amigo de FooBase
:
class FooBase
{
protected:
void fooBase(void);
friend class Foo;
};
El punto clave es que protected
concesiones protected
dan acceso a su propia copia del miembro, no a aquellos miembros en cualquier otro objeto. Esta es una idea errónea común, ya que la mayoría de las veces generalizamos y otorgamos acceso protected
al miembro al tipo derivado (sin indicarlo explícitamente solo en sus propias bases ...)
Ahora, eso es por una razón, y en general, no debe acceder al miembro en una rama diferente de la jerarquía, ya que puede romper las invariantes de las que dependen otros objetos. Considere un tipo que realiza un cálculo costoso en algún miembro de datos grandes (protegido) y dos tipos derivados que almacenan en caché el resultado siguiendo diferentes estrategias:
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();
}
};
El tipo cache_on_read
captura modificaciones en los datos y marca el resultado como no válido, por lo que se vuelve a calcular la siguiente lectura del valor. Este es un buen enfoque si el número de escrituras es relativamente alto, ya que solo realizamos el cálculo a pedido (es decir, múltiples modificaciones no desencadenarán recálculos). cache_on_write
precalcula el resultado por adelantado, lo que podría ser una buena estrategia si el número de escrituras es pequeño y desea costos determinísticos para la lectura (piense en baja latencia en las lecturas).
Ahora, volviendo al problema original. Ambas estrategias de caché mantienen un conjunto más estricto de invariantes que la base. En el primer caso, la invariante adicional es que el cached
es true
solo si los data
no se han modificado después de la última lectura. En el segundo caso, la invariante adicional es que result_value
es el valor de la operación en todo momento.
Si un tercer tipo derivado tomara como referencia una base
y accediera a los data
para escribir (si lo permitiera la protected
), se rompería con las invariantes de los tipos derivados.
Dicho esto, la especificación del lenguaje se rompe (opinión personal) ya que deja una puerta trasera para lograr ese resultado particular. En particular, si crea un puntero al miembro de un miembro desde una base en un tipo derivado, el acceso se verifica en derived
, pero el puntero devuelto es un puntero al miembro de la base
, que se puede aplicar a cualquier objeto base
:
class base {
protected:
int x;
};
struct derived : base {
static void modify( base& b ) {
// b.x = 5; // error!
b.*(&derived::x) = 5; // allowed ?!?!?!
}
}
En ambos ejemplos Foo
hereda un método protegido fooBase
. Sin embargo, en el primer ejemplo, intenta acceder al método protegido de la misma clase ( Foo::foo
llama a Foo::fooBase
), mientras que en el segundo ejemplo intenta acceder a un método protegido de otra clase que no está declarada como clase de amigo ( Foo::foo
intenta llamar a FooBase::fooBase
, que falla, el último está protegido).
En el primer ejemplo, pasa un objeto de tipo Foo, que obviamente hereda el método fooBase () y así puede llamarlo. En el segundo ejemplo, intenta llamar a una función protegida, simplemente así, independientemente del contexto en el que no pueda llamar a una función protegida desde una instancia de clase donde se declare así. En el primer ejemplo, hereda el método protegido fooBase, por lo que tiene derecho a llamarlo dentro del contexto Foo
Las preguntas frecuentes de C ++ resumen bien este tema:
[Usted] puede elegir sus propios bolsillos, pero no puede elegir los bolsillos de su padre ni los bolsillos de su hermano.
Tiendo a ver las cosas en términos de conceptos y mensajes. Si su método FooBase en realidad se llamaba "SendMessage" y Foo era "EnglishSpeakingPerson" y FooBase era SpeakingPerson, su declaración protegida tiene la intención de restringir SendMessage a entre EnglishSpeakingPersons (y subclases, por ejemplo: AmericanEnglishSpeakingPerson, AustralianEnglishSpeakingPerson). Otro tipo FrenchSpeakingPerson derivado de SpeakingPerson no podría recibir un SendMessage, a menos que declarara FrenchSpeakingPerson como amigo, donde ''friend'' significaba que FrenchSpeakingPerson tiene una capacidad especial para recibir SendMessage de EnglishSpeakingPerson (es decir, puede entender inglés).